-
-
Save YehorPytomets/1973c0fd53226d6f0c381214c06ded84 to your computer and use it in GitHub Desktop.
Complex structure of a Vue component with several local and imported composables. Code fragment of the `Vue 2 to Vue 3 migration tips` article
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
- The script of the component has the following structure: | |
- (1) - the `beforeRouteEnter` hook. | |
- (2) - component doc. | |
- (3) - external composables setup. | |
- (4) - local composables and computed properties setup. | |
- (5) - `defineExpose` setup. | |
- (6) - local `useTemplate()` composable. | |
- (7) - local `useWithUser()` composable. | |
--> | |
<script> | |
import TemplateService from '@templates/service/template-service'; | |
import COMMON_NAMED_ROUTES from '@common/router/common-named-routes'; | |
import useTemplatesStore from '@templates/stores/templates'; | |
export default { | |
/** | |
* (1) Fetches the template data before entering the route to pass to the created component. | |
* | |
* <p>This hook is not supported by Composition API, see for more: | |
* - [Comment on beforeRouteEnter](https://github.com/vuejs/rfcs/pull/78#issuecomment-522545475); | |
* - [Discussion on beforeRouteEnter](https://github.com/vuejs/rfcs/discussions/302#discussioncomment-1629410). | |
* | |
* @param to route location we are navigating to | |
* @param from route location we are navigating from | |
* @param next callback to be executed once the navigation completes. Receives the component instance | |
* as the parameter | |
*/ | |
beforeRouteEnter(to, from, next) { | |
const templatesStore = useTemplatesStore(); | |
const templates = templatesStore.templates; | |
const templateId = to.params.id; | |
const is2CoRedirect = to.params.is2CoRedirect; | |
const index = templates.findIndex(template => template.id === templateId); | |
if (index >= 0) { | |
next(vm => vm.setTemplate(templates[index], is2CoRedirect)); | |
} else { | |
TemplateService.fetchById(templateId) | |
.then(template => next(vm => vm.setTemplate(template, is2CoRedirect))) | |
.catch(() => next(COMMON_NAMED_ROUTES.ERROR.UNKNOWN)); | |
} | |
}, | |
}; | |
</script> | |
<script setup> | |
import {computed, readonly, ref} from 'vue'; | |
import TemplatesLayout from '@templates/components/TemplatesLayout'; | |
import TemplateView from '@templates/components/template-view/TemplateView'; | |
import PurchasesService from '@templates/service/purchases-service'; | |
import AfterPurchaseDialog from '@templates/components/template-view/AfterPurchaseDialog'; | |
import {signInAndRedirectRoute} from '@common/router/sign-in'; | |
import useTemplatesAuthStore from '@templates/stores/templates-auth'; | |
import useFirebaseAuthStore from '@common/stores/firebase-auth'; | |
import useLayoutStore from '@common/stores/layout'; | |
import {useRoute} from 'vue-router'; | |
import {appBaseUrl} from '@common/router/app-base-url'; | |
import NAMED_ROUTES from '@templates/router/named-routes'; | |
import useI18next from '@common/i18next'; | |
import {TranslationComponent} from 'i18next-vue'; | |
/** | |
* (2) A component that displays the template view page to the user. The ID of the template to | |
* display is passed as a route property. | |
*/ | |
// (3) External common dependencies: | |
const route = useRoute(); | |
const {t} = useI18next({ | |
namespace: 'templates', | |
keyPrefix: 'templateView', | |
}); | |
// (4) Local reactive data and functions: | |
const { | |
afterPurchaseDialog, | |
template, | |
waitingForDownload, | |
isPurchased, | |
setTemplate, | |
downloadTemplate, | |
purchaseTemplate | |
} = useTemplate(route); | |
const {notSignedIn, canDownloadForFree, downloadBtnLabel} = useWithUser({template, t}); | |
const signInRoute = computed(() => signInAndRedirectRoute(route)); | |
// (5) Makes `setTemplate()` available in `beforeRouteEnter()`: | |
defineExpose({ | |
setTemplate, | |
}); | |
/** (6) Creates and exposes template-related reactive data and functions. */ | |
function useTemplate(route) { | |
/** | |
* References the `AfterPurchaseDialog` in the template. | |
* {@type {Ref<AfterPurchaseDialog>}} | |
*/ | |
const afterPurchaseDialog = ref(null); | |
/** @type {Ref<?TemplateRecord>} */ | |
const template = ref(null); | |
/** | |
* Indicates whether a request for a template download URL has been sent | |
* and the response hasn't been received yet. | |
*/ | |
const waitingForDownload = ref(false); | |
const {showSplashScreen, hideSplashScreen} = useLayoutStore(); | |
const templatesAuthStore = useTemplatesAuthStore(); | |
const isPurchased = computed(() => !!template.value && templatesAuthStore.purchased(template.value.id)); | |
/** | |
* Sets the data obtained after entering this page. | |
* | |
* @param {TemplateRecord} newTemplate a new template to set | |
* @param {boolean} is2CoRedirect indicates that entering this page is a result of a | |
* redirect after placing an order in the 2Checkout payment system | |
*/ | |
function setTemplate(newTemplate, is2CoRedirect) { | |
template.value = newTemplate; | |
if (is2CoRedirect) { | |
handle2CheckoutRedirect(template.value.id); | |
} | |
} | |
async function downloadTemplate() { | |
if (!!waitingForDownload.value) { | |
return; | |
} | |
waitingForDownload.value = true; | |
try { | |
const downloadUrl = await TemplateService.getDownloadUrl(template.value.id); | |
const a = document.createElement('a'); | |
a.style.display = 'none'; | |
document.body.appendChild(a); | |
a.href = downloadUrl; | |
a.click(); | |
document.body.removeChild(a); | |
} finally { | |
waitingForDownload.value = false; | |
} | |
} | |
function purchaseTemplate() { | |
showSplashScreen(); | |
const returnUrl = appBaseUrl(route.fullPath); | |
PurchasesService.startPurchase(template.value.id, returnUrl) | |
.then(buyLink => window.location.href = buyLink) | |
.catch(e => { | |
console.error(e); | |
hideSplashScreen(); | |
}); | |
} | |
/** | |
* Checks whether the currently authenticated user has purchased a template with a given ID. | |
* If it has, perform template download immediately, otherwise displays a notification saying | |
* that a template download link will be sent to a user's email address as soon as a payment | |
* will be processed. | |
* | |
* @param {string} templateId the ID of a template which purchase has been performed | |
*/ | |
function handle2CheckoutRedirect(templateId) { | |
if (templatesAuthStore.purchased(templateId)) { | |
downloadTemplate(); | |
} else { | |
afterPurchaseDialog.value.open(); | |
} | |
} | |
return { | |
afterPurchaseDialog, | |
template: readonly(template), | |
waitingForDownload: readonly(waitingForDownload), | |
isPurchased, | |
setTemplate, | |
downloadTemplate, | |
purchaseTemplate, | |
}; | |
} | |
/** (7) Creates and exposes template-related reactive data and functions in relation to the authenticated user. */ | |
function useWithUser({template, t}) { | |
const firebaseAuthStore = useFirebaseAuthStore(); | |
const signedIn = computed(() => !!firebaseAuthStore.initialized && !!firebaseAuthStore.user); | |
const notSignedIn = computed(() => !!firebaseAuthStore.initialized && !firebaseAuthStore.user); | |
const canDownloadForFree = computed(() => !!signedIn.value && !!template.value && !template.value.price); | |
const downloadBtnLabel = computed(() => !!canDownloadForFree.value | |
? t('downloadBtn.freeTemplate') | |
: t('downloadBtn.purchasedTemplate'), | |
); | |
return { | |
notSignedIn, | |
canDownloadForFree, | |
downloadBtnLabel, | |
}; | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment