fix: added user dropdown and made sidebar static
This commit is contained in:
parent
b77810322d
commit
78870ba10b
7 changed files with 240 additions and 31 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isFCSite.data" class="flex h-screen w-screen">
|
<div v-if="isFCSite.data && user.name" class="flex h-screen w-screen">
|
||||||
<div class="h-full border-r bg-gray-50">
|
<div class="h-full border-r bg-gray-50">
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,9 +16,12 @@
|
||||||
import PageNotFound from './pages/PageNotFound.vue'
|
import PageNotFound from './pages/PageNotFound.vue'
|
||||||
import AppSidebar from '@/components/AppSidebar.vue'
|
import AppSidebar from '@/components/AppSidebar.vue'
|
||||||
import { Dialogs } from '@/dialogs.js'
|
import { Dialogs } from '@/dialogs.js'
|
||||||
|
import { getSession } from '@/session.js'
|
||||||
import { Toasts, createResource } from 'frappe-ui'
|
import { Toasts, createResource } from 'frappe-ui'
|
||||||
import { provide } from 'vue'
|
import { provide } from 'vue'
|
||||||
|
|
||||||
|
const { isFCSite, user } = getSession()
|
||||||
|
|
||||||
const team = createResource({
|
const team = createResource({
|
||||||
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
|
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
|
||||||
params: { method: 'team.info' },
|
params: { method: 'team.info' },
|
||||||
|
|
@ -26,12 +29,5 @@ const team = createResource({
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isFCSite = createResource({
|
|
||||||
url: 'frappe.integrations.frappe_providers.frappecloud_billing.is_fc_site',
|
|
||||||
cache: 'isFCSite',
|
|
||||||
auto: true,
|
|
||||||
transform: (data) => Boolean(data),
|
|
||||||
})
|
|
||||||
|
|
||||||
provide('team', team)
|
provide('team', team)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative flex h-full flex-col justify-between transition-all duration-300 ease-in-out"
|
class="relative flex h-full flex-col justify-between transition-all duration-300 ease-in-out w-[220px]"
|
||||||
:class="isSidebarCollapsed ? 'w-12' : 'w-[220px]'"
|
|
||||||
>
|
>
|
||||||
<div class="flex-1 mt-3 overflow-y-auto">
|
<div>
|
||||||
|
<UserDropdown class="p-2" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-y-auto">
|
||||||
<div class="mb-3 flex flex-col">
|
<div class="mb-3 flex flex-col">
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
:label="previousRoute ? 'Back to app' : 'Back'"
|
:label="previousRoute ? 'Back to app' : 'Back'"
|
||||||
icon="chevron-left"
|
icon="arrow-left"
|
||||||
:isCollapsed="isSidebarCollapsed"
|
|
||||||
@click="goBack"
|
@click="goBack"
|
||||||
class="relative mx-2 my-0.5"
|
class="relative mx-2 my-0.5"
|
||||||
/>
|
/>
|
||||||
|
|
@ -19,31 +20,17 @@
|
||||||
:icon="link.icon"
|
:icon="link.icon"
|
||||||
:label="link.label"
|
:label="link.label"
|
||||||
:to="link.to"
|
:to="link.to"
|
||||||
:isCollapsed="isSidebarCollapsed"
|
|
||||||
class="mx-2 my-0.5"
|
class="mx-2 my-0.5"
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2 flex flex-col gap-1">
|
<div class="m-2 text-base text-gray-600 p-2 flex justify-center">
|
||||||
<SidebarLink
|
Powered by Frappe Cloud
|
||||||
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'"
|
|
||||||
:isCollapsed="isSidebarCollapsed"
|
|
||||||
@click="isSidebarCollapsed = !isSidebarCollapsed"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<span class="grid h-4.5 w-4.5 flex-shrink-0 place-items-center">
|
|
||||||
<CollapseSidebarIcon
|
|
||||||
class="h-4.5 w-4.5 text-gray-700 duration-300 ease-in-out"
|
|
||||||
:class="{ '[transform:rotateY(180deg)]': isSidebarCollapsed }"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</SidebarLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import CollapseSidebarIcon from '@/icons/CollapseSidebarIcon.vue'
|
import UserDropdown from '@/components/UserDropdown.vue'
|
||||||
import BillingIcon from '@/icons/BillingIcon.vue'
|
import BillingIcon from '@/icons/BillingIcon.vue'
|
||||||
import CardIcon from '@/icons/CardIcon.vue'
|
import CardIcon from '@/icons/CardIcon.vue'
|
||||||
import Plans from '@/icons/PlansIcon.vue'
|
import Plans from '@/icons/PlansIcon.vue'
|
||||||
|
|
@ -54,7 +41,6 @@ import { useStorage } from '@vueuse/core'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isSidebarCollapsed = useStorage('isSidebarCollapsed', false)
|
|
||||||
const previousRoute = useStorage('previousRoute', null)
|
const previousRoute = useStorage('previousRoute', null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
73
billing/src/components/Apps.vue
Normal file
73
billing/src/components/Apps.vue
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<Popover placement="right-start" class="flex w-full">
|
||||||
|
<template #target="{ togglePopover }">
|
||||||
|
<button
|
||||||
|
:class="[
|
||||||
|
active ? 'bg-gray-100' : 'text-gray-800',
|
||||||
|
'group w-full flex h-7 items-center justify-between rounded px-2 text-base hover:bg-gray-100',
|
||||||
|
]"
|
||||||
|
@click.prevent="togglePopover()"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<AppsIcon class="size-4" />
|
||||||
|
<span class="whitespace-nowrap">
|
||||||
|
{{ 'Apps' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<FeatherIcon name="chevron-right" class="size-4 text-gray-600" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-3 justify-between mx-3 p-2 rounded-lg border border-gray-100 bg-white shadow-xl"
|
||||||
|
>
|
||||||
|
<div v-for="app in apps.data" :key="app.name">
|
||||||
|
<a
|
||||||
|
:href="app.route"
|
||||||
|
class="flex flex-col gap-1.5 rounded justify-center items-center py-2 px-1 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<img class="size-8" :src="app.logo" />
|
||||||
|
<div class="text-sm text-gray-700" @click="app.onClick">
|
||||||
|
{{ app.title }}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import AppsIcon from '@/icons/AppsIcon.vue'
|
||||||
|
import { FeatherIcon, Popover, createResource } from 'frappe-ui'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
active: Boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
const apps = createResource({
|
||||||
|
url: 'frappe.apps.get_apps',
|
||||||
|
cache: 'apps',
|
||||||
|
auto: true,
|
||||||
|
transform: (data) => {
|
||||||
|
let _apps = [
|
||||||
|
{
|
||||||
|
name: 'frappe',
|
||||||
|
logo: '/assets/frappe/images/framework.png',
|
||||||
|
title: 'Desk',
|
||||||
|
route: '/app',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
data.map((app) => {
|
||||||
|
if (app.name === 'crm') return
|
||||||
|
_apps.push({
|
||||||
|
name: app.name,
|
||||||
|
logo: app.logo,
|
||||||
|
title: app.title,
|
||||||
|
route: app.route,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return _apps
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
70
billing/src/components/UserDropdown.vue
Normal file
70
billing/src/components/UserDropdown.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<Dropdown :options="dropdownOptions" v-bind="$attrs">
|
||||||
|
<template v-slot="{ open }">
|
||||||
|
<button
|
||||||
|
class="flex h-12 items-center rounded-md py-2 duration-300 ease-in-out w-52 px-2"
|
||||||
|
:class="open ? 'bg-white shadow-sm' : 'hover:bg-gray-200'"
|
||||||
|
>
|
||||||
|
<FCLogo class="size-8 flex-shrink-0 rounded" />
|
||||||
|
<div class="flex flex-1 flex-col text-left duration-300 ease-in-out ml-2 w-auto">
|
||||||
|
<div class="text-base font-medium leading-none text-gray-900">
|
||||||
|
{{ 'Billing' }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-sm leading-none text-gray-700">
|
||||||
|
{{ user?.fullName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="duration-300 ease-in-out ml-2 w-auto">
|
||||||
|
<FeatherIcon
|
||||||
|
name="chevron-down"
|
||||||
|
class="size-4 text-gray-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FCLogo from '@/logo/FCLogo.vue'
|
||||||
|
import Apps from '@/components/Apps.vue'
|
||||||
|
import { getSession } from '@/session.js'
|
||||||
|
import { Dropdown, FeatherIcon } from 'frappe-ui'
|
||||||
|
import { computed, ref, markRaw } from 'vue'
|
||||||
|
|
||||||
|
const { user, logout } = getSession()
|
||||||
|
|
||||||
|
let dropdownOptions = ref([
|
||||||
|
{
|
||||||
|
group: 'Manage',
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
component: markRaw(Apps),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// icon: 'life-buoy',
|
||||||
|
// label: computed(() => 'Support'),
|
||||||
|
// onClick: () => window.open('https://t.me/frappecrm', '_blank'),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// icon: 'book-open',
|
||||||
|
// label: computed(() => 'Docs'),
|
||||||
|
// onClick: () => window.open('https://docs.frappe.io/crm', '_blank'),
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Others',
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
icon: 'log-out',
|
||||||
|
label: computed(() => 'Log out'),
|
||||||
|
onClick: () => logout.submit(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
</script>
|
||||||
11
billing/src/icons/AppsIcon.vue
Normal file
11
billing/src/icons/AppsIcon.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
class=""
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M13.8496 4.69692L12.0062 6.54029C11.8109 6.73555 11.4944 6.73555 11.2991 6.54028L9.45572 4.69692C9.26046 4.50166 9.26046 4.18508 9.45572 3.98981L11.2991 2.14645C11.4944 1.95118 11.8109 1.95118 12.0062 2.14645L13.8496 3.98981C14.0448 4.18507 14.0448 4.50166 13.8496 4.69692ZM14.5567 3.28271C15.1425 3.86849 15.1425 4.81824 14.5567 5.40403L12.7133 7.24739C12.1275 7.83318 11.1778 7.83318 10.592 7.24739L8.74862 5.40402C8.16283 4.81824 8.16283 3.86849 8.74862 3.28271L10.592 1.43934C11.1778 0.853553 12.1275 0.853554 12.7133 1.43934L14.5567 3.28271ZM5.60691 4.34338C5.60691 5.3394 4.79948 6.14683 3.80346 6.14683C2.80743 6.14683 2 5.3394 2 4.34338C2 3.34736 2.80743 2.53992 3.80346 2.53992C4.79948 2.53992 5.60691 3.34736 5.60691 4.34338ZM6.60691 4.34338C6.60691 5.89168 5.35176 7.14683 3.80346 7.14683C2.25515 7.14683 1 5.89168 1 4.34338C1 2.79507 2.25515 1.53992 3.80346 1.53992C5.35176 1.53992 6.60691 2.79507 6.60691 4.34338ZM12.9565 10.3897H10.3495C10.0734 10.3897 9.84954 10.6136 9.84954 10.8897V13.4966C9.84954 13.7728 10.0734 13.9966 10.3495 13.9966H12.9565C13.2326 13.9966 13.4565 13.7728 13.4565 13.4966V10.8897C13.4565 10.6136 13.2326 10.3897 12.9565 10.3897ZM10.3495 9.38971C9.52112 9.38971 8.84954 10.0613 8.84954 10.8897V13.4966C8.84954 14.325 9.52111 14.9966 10.3495 14.9966H12.9565C13.7849 14.9966 14.4565 14.325 14.4565 13.4966V10.8897C14.4565 10.0613 13.7849 9.38971 12.9565 9.38971H10.3495ZM2.5 10.3897H5.10691C5.38305 10.3897 5.60691 10.6136 5.60691 10.8897V13.4966C5.60691 13.7728 5.38306 13.9966 5.10691 13.9966H2.5C2.22386 13.9966 2 13.7728 2 13.4966V10.8897C2 10.6136 2.22386 10.3897 2.5 10.3897ZM1 10.8897C1 10.0613 1.67157 9.38971 2.5 9.38971H5.10691C5.93534 9.38971 6.60691 10.0613 6.60691 10.8897V13.4966C6.60691 14.325 5.93534 14.9966 5.10691 14.9966H2.5C1.67157 14.9966 1 14.325 1 13.4966V10.8897Z"
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
12
billing/src/logo/FCLogo.vue
Normal file
12
billing/src/logo/FCLogo.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M25.5561 0.0200195H6.44394C2.88505 0.0200195 0 2.90507 0 6.46396V25.5761C0 29.135 2.88505 32.02 6.44394 32.02H25.5561C29.115 32.02 32 29.135 32 25.5761V6.46396C32 2.90507 29.115 0.0200195 25.5561 0.0200195Z"
|
||||||
|
fill="#25B9FF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M25.8271 13.4145C24.5494 11.5769 22.3959 10.5145 20.1564 10.6581C19.1945 8.92099 17.4143 7.77249 15.3327 7.64329C13.8827 7.55715 12.4184 8.05962 11.2842 9.03584C10.509 9.71058 9.94908 10.4858 9.60453 11.3615C9.40355 11.8784 8.94415 12.2086 8.45604 12.2086H5.0249V15.0798H8.45604C10.1357 15.0798 11.6431 14.0318 12.2748 12.4096C12.4471 11.9645 12.7342 11.5769 13.1649 11.2036C13.7248 10.7155 14.4569 10.4571 15.146 10.5002C16.1797 10.572 16.9118 11.0457 17.3712 11.6343C17.8594 12.1799 18.1321 13.0269 18.29 13.8739C19.2088 13.6155 20.2138 13.4001 21.1756 13.558C21.9652 13.6873 22.6974 14.1036 23.2286 14.7209C23.3147 14.8214 23.4009 14.9219 23.4726 15.0367C24.1617 16.0273 24.2909 17.2476 23.8315 18.4535C23.4152 19.5733 21.8791 20.5926 20.6014 20.5926H10.6095C9.24563 20.5926 8.12584 19.5733 7.95357 18.2669H5.08233C5.26896 21.1668 7.66645 23.4638 10.6095 23.4638H20.6158C23.085 23.4638 25.6835 21.7124 26.5305 19.4728C27.3201 17.3768 27.0617 15.1659 25.8414 13.4001L25.8271 13.4145Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
61
billing/src/session.js
Normal file
61
billing/src/session.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { createResource } from 'frappe-ui'
|
||||||
|
import router from '@/router'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
let user = ref(null)
|
||||||
|
const isLoggedIn = computed(() => !!user.value)
|
||||||
|
|
||||||
|
export function getSession() {
|
||||||
|
function sessionUser() {
|
||||||
|
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
|
let _sessionUser = cookies.get('user_id')
|
||||||
|
let _fullName = cookies.get('full_name')
|
||||||
|
let _imageURL = cookies.get('user_image')
|
||||||
|
if (_sessionUser === 'Guest') {
|
||||||
|
_sessionUser = null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: _sessionUser,
|
||||||
|
fullName: _fullName,
|
||||||
|
imageURL: _imageURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFCSite = createResource({
|
||||||
|
url: 'frappe.integrations.frappe_providers.frappecloud_billing.is_fc_site',
|
||||||
|
cache: 'isFCSite',
|
||||||
|
auto: true,
|
||||||
|
transform: (data) => Boolean(data),
|
||||||
|
})
|
||||||
|
|
||||||
|
user.value = sessionUser()
|
||||||
|
|
||||||
|
const login = createResource({
|
||||||
|
url: 'login',
|
||||||
|
onError() {
|
||||||
|
throw new Error('Invalid email or password')
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
user.value = sessionUser()
|
||||||
|
login.reset()
|
||||||
|
router.replace({ path: '/' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const logout = createResource({
|
||||||
|
url: 'logout',
|
||||||
|
onSuccess() {
|
||||||
|
isFCSite.reload()
|
||||||
|
user.value = null
|
||||||
|
window.location.href = '/'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
isLoggedIn,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
isFCSite,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue