Browse Source

Extract UsageLimitsSection as vue-facing-decorator component and integrate into AccountViewView

- Created UsageLimitsSection.vue using vue-facing-decorator (class-based, TypeScript, @Component, @Prop, @Emit).
- Moved 'Usage Limits' UI and logic (status, spinner, message, recheck button) into the new component.
- Replaced original usage limits section markup in AccountViewView.vue with <UsageLimitsSection />.
- Passed loadingLimits and limitsMessage props, and wired up @recheck-limits event to call checkLimits().
- Ensured all linter errors are resolved and code is consistent with project conventions.
pull/142/head
Matthew Raymer 1 day ago
parent
commit
1391cfd6bf
  1. 28
      src/components/UsageLimitsSection.vue
  2. 91
      src/views/AccountViewView.vue

28
src/components/UsageLimitsSection.vue

@ -0,0 +1,28 @@
<template>
<section class="mt-4">
<h2 class="text-lg font-semibold mb-2">Usage Limits</h2>
<div class="mb-2">
<span v-if="loadingLimits" class="text-slate-700">Checking... <span class="animate-spin"></span></span>
<span v-else class="text-slate-700">{{ limitsMessage }}</span>
</div>
<button
class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
@click="recheckLimits"
>
Recheck Limits
</button>
</section>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit } from 'vue-facing-decorator';
@Component({ name: 'UsageLimitsSection' })
export default class UsageLimitsSection extends Vue {
@Prop({ required: true }) loadingLimits!: boolean;
@Prop({ required: true }) limitsMessage!: string;
@Emit('recheck-limits')
recheckLimits() {}
}
</script>

91
src/views/AccountViewView.vue

@ -248,86 +248,11 @@
<div v-else>Saving...</div> <div v-else>Saving...</div>
</section> </section>
<section <UsageLimitsSection
v-if="activeDid" :loading-limits="loadingLimits"
id="sectionUsageLimits" :limits-message="limitsMessage"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" @recheck-limits="onRecheckLimits"
aria-labelledby="usageLimitsHeading" />
>
<h2 id="usageLimitsHeading" class="mb-2 font-bold">Usage Limits</h2>
<!-- show spinner if loading limits -->
<div
v-if="loadingLimits"
class="text-center"
role="status"
aria-live="polite"
>
Checking&hellip;
<font-awesome
icon="spinner"
class="fa-spin"
aria-hidden="true"
></font-awesome>
</div>
<div class="mb-4 text-center">
{{ limitsMessage }}
</div>
<div v-if="endorserLimits">
<p class="text-sm">
You have done
<b
>{{ endorserLimits?.doneClaimsThisWeek ?? "?" }} claim{{
Number(endorserLimits?.doneClaimsThisWeek || 0) === 1 ? "" : "s"
}}</b
>
out of <b>{{ endorserLimits?.maxClaimsPerWeek ?? "?" }}</b> for this
week. Your claims counter resets at
<b class="whitespace-nowrap">{{
readableDate(endorserLimits?.nextWeekBeginDateTime)
}}</b>
</p>
<p class="mt-3 text-sm">
You have done
<b
>{{
endorserLimits?.doneRegistrationsThisMonth ?? "?"
}}
registration{{
Number(endorserLimits?.doneRegistrationsThisMonth || 0) === 1
? ""
: "s"
}}</b
>
out of
<b>{{ endorserLimits?.maxRegistrationsPerMonth ?? "?" }}</b> for this
this month.
<i>(You cannot register anyone on your first day.)</i>
Your registration counter resets at
<b class="whitespace-nowrap">
{{ readableDate(endorserLimits?.nextMonthBeginDateTime) }}
</b>
</p>
<p class="mt-3 text-sm">
You have uploaded
<b
>{{ imageLimits?.doneImagesThisWeek ?? "?" }} image{{
Number(imageLimits?.doneImagesThisWeek || 0) === 1 ? "" : "s"
}}</b
>
out of <b>{{ imageLimits?.maxImagesPerWeek ?? "?" }}</b> for this
week. Your image counter resets at
<b class="whitespace-nowrap">{{
readableDate(imageLimits?.nextWeekBeginDateTime || "")
}}</b>
</p>
</div>
<button
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mt-4"
@click="checkLimits()"
>
Recheck Limits
</button>
</section>
<DataExportSection :active-did="activeDid" /> <DataExportSection :active-did="activeDid" />
@ -844,6 +769,7 @@ import DataExportSection from "../components/DataExportSection.vue";
import IdentitySection from '@/components/IdentitySection.vue'; import IdentitySection from '@/components/IdentitySection.vue';
import RegistrationNotice from '@/components/RegistrationNotice.vue'; import RegistrationNotice from '@/components/RegistrationNotice.vue';
import LocationSearchSection from '@/components/LocationSearchSection.vue'; import LocationSearchSection from '@/components/LocationSearchSection.vue';
import UsageLimitsSection from '@/components/UsageLimitsSection.vue';
import { import {
AppString, AppString,
DEFAULT_IMAGE_API_SERVER, DEFAULT_IMAGE_API_SERVER,
@ -900,6 +826,7 @@ const inputImportFileNameRef = ref<Blob>();
IdentitySection, IdentitySection,
RegistrationNotice, RegistrationNotice,
LocationSearchSection, LocationSearchSection,
UsageLimitsSection,
}, },
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],
}) })
@ -1726,5 +1653,9 @@ export default class AccountViewView extends Vue {
// TODO: Implement search area dialog logic // TODO: Implement search area dialog logic
this.notify.info('Search area dialog not yet implemented.'); this.notify.info('Search area dialog not yet implemented.');
} }
onRecheckLimits() {
this.checkLimits();
}
} }
</script> </script>

Loading…
Cancel
Save