Skip to content

Instantly share code, notes, and snippets.

@YehorPytomets
Last active October 21, 2022 07:54
Show Gist options
  • Save YehorPytomets/1973c0fd53226d6f0c381214c06ded84 to your computer and use it in GitHub Desktop.
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
<!--
- 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