+ * #### Properties
+ *
+ * @param {string} data Contains array of asset entries.
+ * @param {string} pagination Contains pagination data.
+ *
+ * @since v0.5.0
+ */
+export interface AssetEntry {
+ transactionHash: string;
+ userAddress: string;
+ creationBlock: number;
+ assetId: string;
+ amount: string;
+}
diff --git a/runtime/dapp-frontend-vue/src/router.ts b/runtime/dapp-frontend-vue/src/router.ts
index 71fdffa4..79e66ebd 100644
--- a/runtime/dapp-frontend-vue/src/router.ts
+++ b/runtime/dapp-frontend-vue/src/router.ts
@@ -117,6 +117,16 @@ export const createRouter = ($store: any): VueRouter => {
},
component: () => import("./views/Dashboard/Dashboard.vue"),
},
+
+ {
+ path: "/medals",
+ name: "app.medals",
+ meta: {
+ layout: "app/default",
+ middleware: [auth],
+ },
+ component: () => import("./views/Medals/Medals.vue"),
+ },
{
path: "/settings",
name: "app.settings",
diff --git a/runtime/dapp-frontend-vue/src/services/RewardsService.ts b/runtime/dapp-frontend-vue/src/services/RewardsService.ts
new file mode 100644
index 00000000..2f6a50b2
--- /dev/null
+++ b/runtime/dapp-frontend-vue/src/services/RewardsService.ts
@@ -0,0 +1,56 @@
+/**
+ * This file is part of dHealth dApps Framework shared under LGPL-3.0
+ * Copyright (C) 2022-present dHealth Network, All rights reserved.
+ *
+ * @package dHealth dApps Framework
+ * @subpackage Vue Frontend
+ * @author dHealth Network
+ * @license LGPL-3.0
+ */
+
+// internal dependencies
+import { BackendService } from "./BackendService";
+import { HttpRequestHandler } from "../kernel/remote/HttpRequestHandler";
+import { AssetDTO } from "../models/AssetDTO";
+
+/**
+ * @class RewardsService
+ * @description This class handles backend requests for the `/assets`
+ * namespace and endpoints related to *social platforms*.
+ *
+ * @since v0.5.0
+ */
+export class RewardsService extends BackendService {
+ /**
+ * This property sets the request handler used for the implemented
+ * requests. This handler forwards the execution of the request to
+ * `axios`.
+ *
+ * @access protected
+ * @returns {HttpRequestHandler}
+ */
+ protected get handler(): HttpRequestHandler {
+ return new HttpRequestHandler();
+ }
+
+ /**
+ * This method fetches the social platform configuration objects
+ * from the backend runtime using the `/social/platforms` API.
+ *
+ * @access public
+ * @async
+ * @returns {Promise} An array of social platform configuration objects.
+ */
+ public async getAssetsByAddress(address: string): Promise {
+ // fetch from backend runtime
+ const response = await this.handler.call(
+ this.getUrl(`assets/${address}`),
+ "GET",
+ undefined, // no-body
+ { withCredentials: true, credentials: "include" } // no-headers
+ );
+
+ // return the list of identifiers
+ return response.data;
+ }
+}
diff --git a/runtime/dapp-frontend-vue/src/state/store/RewardsModule.ts b/runtime/dapp-frontend-vue/src/state/store/RewardsModule.ts
new file mode 100644
index 00000000..e982e1a4
--- /dev/null
+++ b/runtime/dapp-frontend-vue/src/state/store/RewardsModule.ts
@@ -0,0 +1,80 @@
+/**
+ * This file is part of dHealth dApps Framework shared under LGPL-3.0
+ * Copyright (C) 2022-present dHealth Network, All rights reserved.
+ *
+ * @package dHealth dApps Framework
+ * @subpackage Vuex Store
+ * @author dHealth Network
+ * @license LGPL-3.0
+ */
+
+// external dependencies
+import Vue from "vue";
+import { ActionContext } from "vuex";
+
+// internal dependencies
+import { RootState } from "./Store";
+import { AwaitLock } from "../AwaitLock";
+import { RewardsService } from "../../services/RewardsService";
+import { AssetEntry } from "@/models/AssetDTO";
+
+// creates an "async"-lock for state of pending initialization
+// this will be kept *locally* to this store module implementation
+const Lock = AwaitLock.create();
+
+export interface AssetsModuleState {
+ initialized: boolean;
+ userAssets: AssetEntry[];
+}
+
+/**
+ *
+ */
+export type AssetsModuleContext = ActionContext;
+
+export const AssetsModule = {
+ // this store module is namespaced, meaning the
+ // module name must be included when calling a
+ // mutation, getter or action, i.e. "integrations/getIntegrations".
+ namespaced: true,
+ state: (): AssetsModuleState => ({
+ initialized: false,
+ userAssets: [],
+ }),
+
+ getters: {
+ isLoading: (state: AssetsModuleState): boolean => !state.initialized,
+ getAssets: (state: AssetsModuleState): AssetEntry[] => state.userAssets,
+ },
+
+ mutations: {
+ /**
+ *
+ */
+ setInitialized: (state: AssetsModuleState, payload: boolean): boolean =>
+ (state.initialized = payload),
+
+ setAssets: (state: AssetsModuleState, payload: AssetEntry[]) =>
+ (state.userAssets = payload),
+ },
+
+ actions: {
+ /**
+ *
+ */
+ async fetchRewards(
+ context: AssetsModuleContext,
+ address: string
+ ): Promise {
+ const service = new RewardsService();
+ const response = await service.getAssetsByAddress(address);
+ const assets = response.data;
+
+ console.log({ response });
+
+ context.commit("setAssets", assets);
+
+ return assets;
+ },
+ },
+};
diff --git a/runtime/dapp-frontend-vue/src/state/store/Store.ts b/runtime/dapp-frontend-vue/src/state/store/Store.ts
index 3137b97b..25605d55 100644
--- a/runtime/dapp-frontend-vue/src/state/store/Store.ts
+++ b/runtime/dapp-frontend-vue/src/state/store/Store.ts
@@ -18,6 +18,7 @@ import { OAuthModule } from "./OAuthModule";
import { LeaderboardModule } from "./LeaderboardModule";
import { StatisticsModule } from "./StatisticsModule";
import { ActivitiesModule } from "./ActivitiesModule";
+import { AssetsModule } from "./RewardsModule";
/**
* @todo missing interface documentation
@@ -58,6 +59,7 @@ export const createStore = () => {
leaderboard: LeaderboardModule,
statistics: StatisticsModule,
activities: ActivitiesModule,
+ assets: AssetsModule,
},
state: (): RootState => ({
initialized: false,
diff --git a/runtime/dapp-frontend-vue/src/views/Medals/Medals.scss b/runtime/dapp-frontend-vue/src/views/Medals/Medals.scss
new file mode 100644
index 00000000..53bb5363
--- /dev/null
+++ b/runtime/dapp-frontend-vue/src/views/Medals/Medals.scss
@@ -0,0 +1,34 @@
+/**
+ * This file is part of dHealth dApps Framework shared under LGPL-3.0
+ * Copyright (C) 2022-present dHealth Network, All rights reserved.
+ *
+ * @package dHealth dApps Framework
+ * @subpackage Vue Frontend
+ * @author dHealth Network
+ * @license LGPL-3.0
+ */
+
+ @import "../../vars.scss";
+
+ .dapp-medals {
+ .boards-title {
+ display: inline-block;
+ background-color: $accent-yellow;
+ color: $black;
+ font-weight: 600;
+ font-size: 42px;
+ line-height: 140%;
+ margin-top: 120px;
+ margin-bottom: 50px;
+ border-radius: 8px;
+ padding: 4px 54px;
+ }
+
+ .dapp-rewards-list {
+ margin-bottom: 105px;
+
+ &:first-child {
+ margin-bottom: 0;
+ }
+ }
+ }
\ No newline at end of file
diff --git a/runtime/dapp-frontend-vue/src/views/Medals/Medals.ts b/runtime/dapp-frontend-vue/src/views/Medals/Medals.ts
new file mode 100644
index 00000000..8762381c
--- /dev/null
+++ b/runtime/dapp-frontend-vue/src/views/Medals/Medals.ts
@@ -0,0 +1,149 @@
+/**
+ * This file is part of dHealth dApps Framework shared under LGPL-3.0
+ * Copyright (C) 2022-present dHealth Network, All rights reserved.
+ *
+ * @package dHealth dApps Framework
+ * @subpackage Vue Frontend
+ * @author dHealth Network
+ * @license LGPL-3.0
+ */
+// external dependencies
+import { Component } from "vue-property-decorator";
+import { mapGetters } from "vuex";
+
+// internal dependencies
+import { MetaView } from "@/views/MetaView";
+import RewardsList from "@/components/RewardsList/RewardsList.vue";
+import { MedalItem } from "@/components/RewardsList/RewardsList";
+import { AssetEntry } from "@/models/AssetDTO";
+
+// style resource
+import "./Medals.scss";
+
+/*
+ * @class Medals
+ * @description This class implements a Vue component to display
+ * rewards page wit
+ *
+ * @since v0.3.0
+ */
+@Component({
+ components: {
+ RewardsList,
+ },
+ computed: {
+ ...mapGetters({
+ currentUserAddress: "auth/getCurrentUserAddress",
+ availableAssets: "assets/getAssets",
+ }),
+ },
+})
+export default class Medals extends MetaView {
+ /**
+ * This property contains the authenticated user's dHealth Account
+ * Address. This field is populated using the Vuex Store after a
+ * successful request to the backend API's `/me` endpoint.
+ *
+ * The `!`-operator tells TypeScript that this value is required
+ * and the *public* access permits the Vuex Store to mutate this
+ * value when it is necessary.
+ *
+ * @access public
+ * @var {string}
+ */
+ public currentUserAddress!: string;
+
+ /**
+ * This property represents vuex getter
+ * for the assets, available for user.
+ *
+ * It's value received from
+ * GET /assets/:address endpoint.
+ *
+ * @access public
+ * @var {AssetEntry[]}
+ */
+ public availableAssets!: AssetEntry[];
+
+ /**
+ * This computed represents all available
+ * medals for the user, initially marked as
+ * *not received*
+ *
+ * @access protected
+ * @returns MedalItem[]
+ */
+ public get knownMedals(): MedalItem[] {
+ return [
+ {
+ image: "medals/10.svg",
+ condition: "Invite 5 users to receive medal!",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: process.env.VUE_APP_ASSETS_BOOST5_IDENTIFIER as string,
+ },
+ {
+ image: "medals/50.svg",
+ condition: "Invite 10 users.",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: process.env.VUE_APP_ASSETS_BOOST10_IDENTIFIER as string,
+ },
+ {
+ image: "medals/10.svg",
+ condition: "Invite 15 users",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: process.env.VUE_APP_ASSETS_BOOST15_IDENTIFIER as string,
+ },
+ {
+ image: "medals/100.svg",
+ condition: "Complete one more workout to get.",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: "fakeIdToShowNotReceivedMedals1",
+ },
+ {
+ image: "medals/50.svg",
+ condition: "Complete 50 kilometers to get.",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: "fakeIdToShowNotReceivedMedals2",
+ },
+ {
+ image: "medals/100.svg",
+ condition: "Complete one more workout to get.",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: "fakeIdToShowNotReceivedMedals3",
+ },
+ {
+ image: "medals/10.svg",
+ condition: "Finish your first 10KM in one go to get!",
+ received: false,
+ relatedActivities: "Running, Walking, Swimming, Cycling",
+ assetId: "fakeIdToShowNotReceivedMedals4",
+ },
+ ];
+ }
+
+ /**
+ * This computed returns medals with
+ * *received* value based on available assets.
+ *
+ * @access protected
+ * @returns MedalItem[]
+ */
+ public get validatedMedals(): MedalItem[] {
+ return this.knownMedals.map((medal: MedalItem) => ({
+ ...medal,
+ received: !!this.availableAssets.find(
+ (asset: AssetEntry) => asset.assetId === medal.assetId
+ ),
+ }));
+ }
+
+ public async mounted() {
+ await this.$store.dispatch("assets/fetchRewards", this.currentUserAddress);
+ }
+}
diff --git a/runtime/dapp-frontend-vue/src/views/Medals/Medals.vue b/runtime/dapp-frontend-vue/src/views/Medals/Medals.vue
new file mode 100644
index 00000000..99e849d1
--- /dev/null
+++ b/runtime/dapp-frontend-vue/src/views/Medals/Medals.vue
@@ -0,0 +1,26 @@
+
+
+