Merge pull request #1317 from pateljannat/trial-signup
feat: billing banner for FC trial sites
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
|
||||
<script>
|
||||
window.csrf_token = '{{ csrf_token }}'
|
||||
window.setup_complete = '{{ setup_complete }}'
|
||||
document.getElementById('seo-content').style.display = 'none';
|
||||
</script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
@@ -62,25 +62,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SidebarLink
|
||||
:link="{
|
||||
label: sidebarStore.isSidebarCollapsed ? 'Expand' : 'Collapse',
|
||||
}"
|
||||
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
||||
@click="toggleSidebar()"
|
||||
class="m-2"
|
||||
>
|
||||
<template #icon>
|
||||
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||
<CollapseSidebar
|
||||
class="h-4.5 w-4.5 text-ink-gray-7 duration-300 ease-in-out"
|
||||
:class="{
|
||||
'[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed,
|
||||
}"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</SidebarLink>
|
||||
<div>
|
||||
<TrialBanner
|
||||
v-if="
|
||||
userResource.data?.user_type == 'System User' &&
|
||||
userResource.data?.is_fc_site
|
||||
"
|
||||
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
||||
/>
|
||||
<SidebarLink
|
||||
:link="{
|
||||
label: sidebarStore.isSidebarCollapsed ? 'Expand' : 'Collapse',
|
||||
}"
|
||||
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
||||
@click="toggleSidebar()"
|
||||
class="m-2"
|
||||
>
|
||||
<template #icon>
|
||||
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||
<CollapseSidebar
|
||||
class="h-4.5 w-4.5 text-ink-gray-7 duration-300 ease-in-out"
|
||||
:class="{
|
||||
'[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed,
|
||||
}"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</SidebarLink>
|
||||
</div>
|
||||
</div>
|
||||
<PageModal
|
||||
v-model="showPageModal"
|
||||
@@ -101,7 +110,7 @@ import { sessionStore } from '@/stores/session'
|
||||
import { useSidebar } from '@/stores/sidebar'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { ChevronRight, Plus } from 'lucide-vue-next'
|
||||
import { createResource, Button } from 'frappe-ui'
|
||||
import { Button, createResource, TrialBanner } from 'frappe-ui'
|
||||
import PageModal from '@/components/Modals/PageModal.vue'
|
||||
|
||||
const { user, sidebarSettings } = sessionStore()
|
||||
|
||||
94
frontend/src/components/Modals/FCVerfiyCodeModal.vue
Normal file
94
frontend/src/components/Modals/FCVerfiyCodeModal.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
size: 'xl',
|
||||
title: __('Login to Frappe Cloud'),
|
||||
actions: [
|
||||
{
|
||||
label: __('Verify'),
|
||||
variant: 'solid',
|
||||
onClick: (close) => {
|
||||
verifyCode(close)
|
||||
},
|
||||
},
|
||||
],
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div>
|
||||
<p>
|
||||
{{ __('We have sent the verificaton code to your email id ') }}
|
||||
<b>{{ props.email }}</b>
|
||||
</p>
|
||||
<FormControl
|
||||
v-model="code"
|
||||
:label="__('Verification Code')"
|
||||
class="mb-4"
|
||||
/>
|
||||
<p>
|
||||
{{ __("Didn't receive the code?") }}
|
||||
<a href="#" @click="resendCode">{{ __('Resend') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { call, Dialog } from 'frappe-ui'
|
||||
import { showToast } from '@/utils'
|
||||
|
||||
const show = defineModel()
|
||||
const code = ref('')
|
||||
|
||||
const props = defineProps({
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const verifyCode = (close) => {
|
||||
if (!code.value) {
|
||||
return
|
||||
}
|
||||
call(
|
||||
'frappe.integrations.frappe_providers.frappecloud_billing.verify_verification_code',
|
||||
{
|
||||
verification_code: code.value,
|
||||
route: window.route,
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
if (data.message.login_token) {
|
||||
close()
|
||||
window.open(
|
||||
`${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${data.message.login_token}`,
|
||||
'_blank'
|
||||
)
|
||||
showToast(
|
||||
__('Frappe Cloud Login Successful'),
|
||||
`<p>${__('You will be redirected to Frappe Cloud soon.')}</p><p>${__(
|
||||
"If you haven't been redirected,"
|
||||
)} <a href="${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${
|
||||
data.message.login_token
|
||||
}" target="_blank">${__('Click here to login')}</a></p>`,
|
||||
'check'
|
||||
)
|
||||
} else {
|
||||
showToast(__('Login failed'), __('Please try again'), 'x')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
showToast(__('Login failed'), __('Please try again'), 'x')
|
||||
})
|
||||
}
|
||||
|
||||
const resendCode = () => {
|
||||
call(
|
||||
'frappe.integrations.frappe_providers.frappecloud_billing.send_verification_code'
|
||||
).catch((err) => {
|
||||
showToast(__('Failed to resend code'), __('Please try again'), 'x')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -59,13 +59,22 @@
|
||||
v-if="userResource.data?.is_moderator"
|
||||
v-model="showSettingsModal"
|
||||
/>
|
||||
<FCVerfiyCodeModal v-if="showFCLoginDialog" :email="verificationEmail" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { Dropdown } from 'frappe-ui'
|
||||
import { call, Dropdown } from 'frappe-ui'
|
||||
import Apps from '@/components/Apps.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertToTitleCase, showToast } from '@/utils'
|
||||
import { usersStore } from '@/stores/user'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { markRaw, watch, ref, onMounted, computed } from 'vue'
|
||||
import SettingsModal from '@/components/Modals/Settings.vue'
|
||||
import { createDialog } from '@/utils/dialogs'
|
||||
import FCVerfiyCodeModal from './Modals/FCVerfiyCodeModal.vue'
|
||||
import {
|
||||
ChevronDown,
|
||||
LogIn,
|
||||
@@ -74,13 +83,8 @@ import {
|
||||
User,
|
||||
Settings,
|
||||
Sun,
|
||||
LogInIcon,
|
||||
} from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertToTitleCase } from '../utils'
|
||||
import { usersStore } from '@/stores/user'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { markRaw, watch, ref, onMounted, computed } from 'vue'
|
||||
import SettingsModal from '@/components/Modals/Settings.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { logout, branding } = sessionStore()
|
||||
@@ -89,6 +93,11 @@ const settingsStore = useSettings()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
const showSettingsModal = ref(false)
|
||||
const theme = ref('light')
|
||||
const $dialog = createDialog
|
||||
const frappeCloudBaseEndpoint = 'https://frappecloud.com'
|
||||
|
||||
const showFCLoginDialog = ref(false)
|
||||
const verificationEmail = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
isCollapsed: {
|
||||
@@ -130,6 +139,13 @@ const userDropdownOptions = computed(() => {
|
||||
return isLoggedIn
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: theme.value === 'light' ? Moon : Sun,
|
||||
label: 'Toggle Theme',
|
||||
onClick: () => {
|
||||
toggleTheme()
|
||||
},
|
||||
},
|
||||
{
|
||||
component: markRaw(Apps),
|
||||
condition: () => {
|
||||
@@ -139,13 +155,6 @@ const userDropdownOptions = computed(() => {
|
||||
else return false
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: theme.value === 'light' ? Moon : Sun,
|
||||
label: 'Toggle Theme',
|
||||
onClick: () => {
|
||||
toggleTheme()
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
label: 'Settings',
|
||||
@@ -156,6 +165,19 @@ const userDropdownOptions = computed(() => {
|
||||
return userResource.data?.is_moderator
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: LogInIcon,
|
||||
label: 'Login to Frappe Cloud',
|
||||
onClick: () => {
|
||||
initiateRequestForLoginToFrappeCloud()
|
||||
},
|
||||
condition: () => {
|
||||
return (
|
||||
userResource.data?.user_type == 'System User' &&
|
||||
userResource.data?.is_fc_site
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: LogOut,
|
||||
label: 'Log out',
|
||||
@@ -180,4 +202,48 @@ const userDropdownOptions = computed(() => {
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const initiateRequestForLoginToFrappeCloud = () => {
|
||||
$dialog({
|
||||
title: __('Login to Frappe Cloud?'),
|
||||
message: __(
|
||||
'Are you sure you want to login to your Frappe Cloud dashboard?'
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: __('Confirm'),
|
||||
variant: 'solid',
|
||||
onClick(close) {
|
||||
requestLoginToFC()
|
||||
close()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const requestLoginToFC = () => {
|
||||
call(
|
||||
'frappe.integrations.frappe_providers.frappecloud_billing.send_verification_code'
|
||||
)
|
||||
.then((data) => {
|
||||
if (data.message.is_user_logged_in) {
|
||||
window.open(
|
||||
`${frappeCloudBaseEndpoint}${data.message.redirect_to}`,
|
||||
'_blank'
|
||||
)
|
||||
return
|
||||
} else {
|
||||
showFCLoginDialog.value = true
|
||||
verificationEmail.value = data.message.email
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
showToast(
|
||||
__('Failed to login to Frappe Cloud'),
|
||||
__('Please try again'),
|
||||
'x'
|
||||
)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default defineConfig({
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
allowedHosts: ['fs'],
|
||||
allowedHosts: ['fs', 'bs'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -22,7 +22,7 @@ from frappe.utils import (
|
||||
from lms.lms.utils import get_average_rating, get_lesson_count
|
||||
from xml.dom.minidom import parseString
|
||||
from lms.lms.doctype.course_lesson.course_lesson import save_progress
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -175,6 +175,7 @@ def get_user_info():
|
||||
user.is_moderator = "Moderator" in user.roles
|
||||
user.is_evaluator = "Batch Evaluator" in user.roles
|
||||
user.is_student = "LMS Student" in user.roles
|
||||
user.is_fc_site = is_fc_site()
|
||||
return user
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import frappe
|
||||
from frappe.utils.telemetry import capture
|
||||
from frappe import _
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from bs4 import BeautifulSoup
|
||||
from frappe import _
|
||||
from frappe.utils.telemetry import capture
|
||||
from frappe.utils import cint
|
||||
|
||||
no_cache = 1
|
||||
|
||||
@@ -17,6 +18,7 @@ def get_context():
|
||||
csrf_token = frappe.sessions.get_csrf_token()
|
||||
frappe.db.commit() # nosemgrep
|
||||
context.csrf_token = csrf_token
|
||||
context.setup_complete = cint(frappe.get_system_settings("setup_complete"))
|
||||
capture("active_site", "lms")
|
||||
context.favicon = frappe.db.get_single_value("Website Settings", "favicon")
|
||||
return context
|
||||
|
||||
Reference in New Issue
Block a user