Add cooldown feature for reputation actions in FalukantService and update UI components
- Introduced a cooldown mechanism for reputation actions, limiting execution to once per configured interval. - Updated FalukantService to handle cooldown logic and return remaining cooldown time. - Enhanced ReputationView component to display cooldown status and prevent action execution during cooldown. - Added translations for cooldown messages in both German and English locales.
This commit is contained in:
@@ -330,6 +330,8 @@ class PreconditionError extends Error {
|
||||
class FalukantService extends BaseService {
|
||||
static KNOWLEDGE_MAX = 99;
|
||||
static REPUTATION_ACTION_DAILY_CAP = Number(process.env.FALUKANT_REPUTATION_ACTION_DAILY_CAP ?? 10);
|
||||
static REPUTATION_ACTION_COOLDOWN_MINUTES = Number(process.env.FALUKANT_REPUTATION_ACTION_COOLDOWN_MINUTES ?? 60);
|
||||
static RANDOM_EVENT_DAILY_ENABLED = String(process.env.FALUKANT_RANDOM_EVENT_DAILY_ENABLED ?? '1') === '1';
|
||||
static COST_CONFIG = {
|
||||
one: { min: 50, max: 5000 },
|
||||
all: { min: 400, max: 40000 }
|
||||
@@ -500,7 +502,7 @@ class FalukantService extends BaseService {
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'character',
|
||||
attributes: ['birthdate', 'health', 'reputation'],
|
||||
attributes: ['id', 'regionId', 'birthdate', 'health', 'reputation'],
|
||||
include: [
|
||||
{
|
||||
model: Relationship,
|
||||
@@ -553,6 +555,16 @@ class FalukantService extends BaseService {
|
||||
const userCharacterIds = userCharacterIdsRows.map(r => r.id);
|
||||
bm('aggregate.userCharacters', { count: userCharacterIds.length, ids: userCharacterIds.slice(0, 5) });
|
||||
|
||||
// Daily random event (once per calendar day per user) -> stored as Notification random_event.*
|
||||
// Frontend already supports JSON-encoded tr: {"tr":"random_event.windfall","amount":123}
|
||||
try {
|
||||
if (FalukantService.RANDOM_EVENT_DAILY_ENABLED) {
|
||||
await this._maybeCreateDailyRandomEvent(falukantUser, user);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Falukant] daily random event failed (non-fatal):', e?.message || e);
|
||||
}
|
||||
|
||||
// Count distinct children for any of the user's characters (as father or mother)
|
||||
let childrenCount = 0;
|
||||
let unbaptisedChildrenCount = 0;
|
||||
@@ -605,6 +617,99 @@ class FalukantService extends BaseService {
|
||||
return falukantUser;
|
||||
}
|
||||
|
||||
async _maybeCreateDailyRandomEvent(falukantUser, user) {
|
||||
if (!falukantUser?.id) return null;
|
||||
|
||||
// Already created today?
|
||||
const since = new Date();
|
||||
since.setHours(0, 0, 0, 0);
|
||||
const already = await Notification.count({
|
||||
where: {
|
||||
userId: falukantUser.id,
|
||||
createdAt: { [Op.gte]: since },
|
||||
[Op.or]: [
|
||||
{ tr: { [Op.like]: 'random_event.%' } },
|
||||
{ tr: { [Op.like]: '%\"tr\":\"random_event.%' } },
|
||||
],
|
||||
},
|
||||
});
|
||||
if (already > 0) return null;
|
||||
|
||||
// Choose an event (guaranteed once/day, random type)
|
||||
const events = [
|
||||
{ id: 'windfall', weight: 25 },
|
||||
{ id: 'theft', weight: 20 },
|
||||
{ id: 'character_illness', weight: 20 },
|
||||
{ id: 'character_recovery', weight: 15 },
|
||||
{ id: 'character_accident', weight: 10 },
|
||||
{ id: 'regional_festival', weight: 10 },
|
||||
];
|
||||
const total = events.reduce((s, e) => s + e.weight, 0);
|
||||
let r = Math.random() * total;
|
||||
let chosen = events[0].id;
|
||||
for (const e of events) {
|
||||
r -= e.weight;
|
||||
if (r <= 0) { chosen = e.id; break; }
|
||||
}
|
||||
|
||||
const payload = { tr: `random_event.${chosen}` };
|
||||
|
||||
return await sequelize.transaction(async (t) => {
|
||||
// Reload current values inside tx
|
||||
const freshUser = await FalukantUser.findByPk(falukantUser.id, { transaction: t });
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: falukantUser.id },
|
||||
include: [
|
||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'], required: false },
|
||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'], required: false },
|
||||
],
|
||||
transaction: t,
|
||||
});
|
||||
|
||||
// Effects (keine harten Datenlöschungen)
|
||||
if (chosen === 'windfall') {
|
||||
const amount = Math.floor(Math.random() * 901) + 100; // 100..1000
|
||||
payload.amount = amount;
|
||||
await updateFalukantUserMoney(falukantUser.id, amount, 'random_event.windfall', falukantUser.id, t);
|
||||
} else if (chosen === 'theft') {
|
||||
const maxLoss = Math.max(0, Math.min(500, Math.floor(Number(freshUser?.money || 0))));
|
||||
const amount = maxLoss > 0 ? (Math.floor(Math.random() * maxLoss) + 1) : 0;
|
||||
payload.amount = amount;
|
||||
if (amount > 0) {
|
||||
await updateFalukantUserMoney(falukantUser.id, -amount, 'random_event.theft', falukantUser.id, t);
|
||||
}
|
||||
} else if (chosen === 'character_illness' || chosen === 'character_recovery' || chosen === 'character_accident') {
|
||||
const name = [character?.definedFirstName?.name, character?.definedLastName?.name].filter(Boolean).join(' ').trim();
|
||||
payload.characterName = name || null;
|
||||
let delta = 0;
|
||||
if (chosen === 'character_illness') delta = -(Math.floor(Math.random() * 11) + 5); // -5..-15
|
||||
if (chosen === 'character_recovery') delta = (Math.floor(Math.random() * 11) + 5); // +5..+15
|
||||
if (chosen === 'character_accident') delta = -(Math.floor(Math.random() * 16) + 10); // -10..-25
|
||||
payload.healthChange = delta > 0 ? `+${delta}` : `${delta}`;
|
||||
if (character) {
|
||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||
await character.update({ health: next }, { transaction: t });
|
||||
}
|
||||
} else if (chosen === 'regional_festival') {
|
||||
const regionId = character?.regionId || falukantUser?.mainBranchRegionId || null;
|
||||
if (regionId) {
|
||||
const region = await RegionData.findByPk(regionId, { attributes: ['name'], transaction: t });
|
||||
payload.regionName = region?.name || null;
|
||||
}
|
||||
}
|
||||
|
||||
// Store notification as JSON string so frontend can interpolate params
|
||||
await Notification.create(
|
||||
{ userId: falukantUser.id, tr: JSON.stringify(payload), shown: false },
|
||||
{ transaction: t }
|
||||
);
|
||||
|
||||
// Make statusbar update (unread count, etc.)
|
||||
try { notifyUser(user.hashedId, 'falukantUpdateStatus', {}); } catch (_) {}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
async getBranches(hashedUserId) {
|
||||
const u = await getFalukantUserOrFail(hashedUserId);
|
||||
const bs = await Branch.findAll({
|
||||
@@ -1610,76 +1715,103 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
|
||||
async sellProduct(hashedUserId, branchId, productId, quality, quantity) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const branch = await getBranchOrFail(user.id, branchId);
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||
if (!character) throw new Error('No character found for user');
|
||||
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
|
||||
if (!stock) throw new Error('Stock not found');
|
||||
const inventory = await Inventory.findAll({
|
||||
where: { quality },
|
||||
include: [
|
||||
{
|
||||
model: ProductType,
|
||||
as: 'productType',
|
||||
required: true,
|
||||
where: { id: productId },
|
||||
include: [
|
||||
{
|
||||
model: Knowledge,
|
||||
as: 'knowledges',
|
||||
required: false,
|
||||
where: { characterId: character.id }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!inventory.length) {
|
||||
throw new Error('No inventory found');
|
||||
}
|
||||
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
||||
if (available < quantity) throw new Error('Not enough inventory available');
|
||||
const item = inventory[0].productType;
|
||||
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
|
||||
const pricePerUnit = await calcRegionalSellPrice(item, knowledgeVal, branch.regionId);
|
||||
// Konsistenz wie sellAll: nur aus Stocks dieses Branches verkaufen und alles atomar ausführen
|
||||
return await sequelize.transaction(async (t) => {
|
||||
const user = await getFalukantUserOrFail(hashedUserId, { transaction: t });
|
||||
const branch = await getBranchOrFail(user.id, branchId);
|
||||
|
||||
// compute cumulative tax (region + ancestors) with political exemptions and inflate price so seller net is unchanged
|
||||
const cumulativeTax = await getCumulativeTaxPercentWithExemptions(user.id, branch.regionId);
|
||||
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
|
||||
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
|
||||
const revenue = quantity * adjustedPricePerUnit;
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id }, transaction: t });
|
||||
if (!character) throw new Error('No character found for user');
|
||||
|
||||
// compute tax and net
|
||||
const taxValue = Math.round((revenue * cumulativeTax / 100) * 100) / 100;
|
||||
const net = Math.round((revenue - taxValue) * 100) / 100;
|
||||
const stocks = await FalukantStock.findAll({
|
||||
where: { branchId: branch.id },
|
||||
attributes: ['id'],
|
||||
transaction: t
|
||||
});
|
||||
const stockIds = stocks.map(s => s.id);
|
||||
if (!stockIds.length) throw new Error('Stock not found');
|
||||
|
||||
// Book net to seller
|
||||
const moneyResult = await updateFalukantUserMoney(user.id, net, `Product sale (net)`, user.id);
|
||||
if (!moneyResult.success) throw new Error('Failed to update money for seller');
|
||||
const inventory = await Inventory.findAll({
|
||||
where: {
|
||||
stockId: { [Op.in]: stockIds },
|
||||
productId,
|
||||
quality
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ProductType,
|
||||
as: 'productType',
|
||||
required: true,
|
||||
where: { id: productId },
|
||||
include: [
|
||||
{
|
||||
model: Knowledge,
|
||||
as: 'knowledges',
|
||||
required: false,
|
||||
where: { characterId: character.id }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['producedAt', 'ASC'], ['id', 'ASC']],
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// Book tax to treasury (if configured)
|
||||
const treasuryId = process.env.TREASURY_FALUKANT_USER_ID;
|
||||
if (treasuryId && taxValue > 0) {
|
||||
const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), taxValue, `Sales tax (${cumulativeTax}%)`, user.id);
|
||||
if (!taxResult.success) throw new Error('Failed to update money for treasury');
|
||||
}
|
||||
let remaining = quantity;
|
||||
for (const inv of inventory) {
|
||||
if (inv.quantity <= remaining) {
|
||||
remaining -= inv.quantity;
|
||||
await inv.destroy();
|
||||
} else {
|
||||
await inv.update({ quantity: inv.quantity - remaining });
|
||||
remaining = 0;
|
||||
break;
|
||||
if (!inventory.length) {
|
||||
throw new Error('No inventory found');
|
||||
}
|
||||
}
|
||||
await this.addSellItem(branchId, user.id, productId, quantity);
|
||||
console.log('[FalukantService.sellProduct] emitting events for user', user.user.hashedId, 'branch', branch?.id);
|
||||
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
||||
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
|
||||
return { success: true };
|
||||
|
||||
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
||||
if (available < quantity) throw new Error('Not enough inventory available');
|
||||
|
||||
const item = inventory[0].productType;
|
||||
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
|
||||
const pricePerUnit = await calcRegionalSellPrice(item, knowledgeVal, branch.regionId);
|
||||
|
||||
// compute cumulative tax (region + ancestors) with political exemptions and inflate price so seller net is unchanged
|
||||
const cumulativeTax = await getCumulativeTaxPercentWithExemptions(user.id, branch.regionId);
|
||||
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
|
||||
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
|
||||
const revenue = quantity * adjustedPricePerUnit;
|
||||
|
||||
// compute tax and net
|
||||
const taxValue = Math.round((revenue * cumulativeTax / 100) * 100) / 100;
|
||||
const net = Math.round((revenue - taxValue) * 100) / 100;
|
||||
|
||||
// Book net to seller (in tx)
|
||||
const moneyResult = await updateFalukantUserMoney(user.id, net, `Product sale (net)`, user.id, t);
|
||||
if (!moneyResult.success) throw new Error('Failed to update money for seller');
|
||||
|
||||
// Book tax to treasury (if configured)
|
||||
const treasuryId = process.env.TREASURY_FALUKANT_USER_ID;
|
||||
if (treasuryId && taxValue > 0) {
|
||||
const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), taxValue, `Sales tax (${cumulativeTax}%)`, user.id, t);
|
||||
if (!taxResult.success) throw new Error('Failed to update money for treasury');
|
||||
}
|
||||
|
||||
let remaining = quantity;
|
||||
for (const inv of inventory) {
|
||||
if (remaining <= 0) break;
|
||||
if (inv.quantity <= remaining) {
|
||||
remaining -= inv.quantity;
|
||||
await inv.destroy({ transaction: t });
|
||||
} else {
|
||||
await inv.update({ quantity: inv.quantity - remaining }, { transaction: t });
|
||||
remaining = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remaining !== 0) {
|
||||
throw new Error(`Inventory deduction mismatch (remaining=${remaining})`);
|
||||
}
|
||||
|
||||
await this.addSellItem(branchId, user.id, productId, quantity, t);
|
||||
|
||||
// notify after successful commit (we can still emit here; worst-case it's slightly early)
|
||||
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
||||
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
async sellAllProducts(hashedUserId, branchId) {
|
||||
@@ -1827,28 +1959,26 @@ class FalukantService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
async addSellItem(branchId, userId, productId, quantity) {
|
||||
async addSellItem(branchId, userId, productId, quantity, transaction = null) {
|
||||
const branch = await Branch.findOne({
|
||||
where: { id: branchId },
|
||||
})
|
||||
;
|
||||
const daySell = await DaySell.findOne({
|
||||
attributes: ['id', 'regionId'],
|
||||
transaction: transaction || undefined
|
||||
});
|
||||
if (!branch) throw new Error(`Branch not found (branchId: ${branchId})`);
|
||||
|
||||
const [daySell, created] = await DaySell.findOrCreate({
|
||||
where: {
|
||||
regionId: branch.regionId,
|
||||
productId: productId,
|
||||
sellerId: userId,
|
||||
}
|
||||
},
|
||||
defaults: { quantity: quantity },
|
||||
transaction: transaction || undefined
|
||||
});
|
||||
if (daySell) {
|
||||
if (!created) {
|
||||
daySell.quantity += quantity;
|
||||
await daySell.save();
|
||||
} else {
|
||||
await DaySell.create({
|
||||
regionId: branch.regionId,
|
||||
productId: productId,
|
||||
sellerId: userId,
|
||||
quantity: quantity,
|
||||
});
|
||||
await daySell.save({ transaction: transaction || undefined });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3301,7 +3431,33 @@ class FalukantService extends BaseService {
|
||||
);
|
||||
const dailyRemaining = Math.max(0, Number(dailyCap) - Number(dailyUsed || 0));
|
||||
|
||||
if (!actionTypes.length) return { dailyCap, dailyUsed: Number(dailyUsed || 0), dailyRemaining, actions: [] };
|
||||
// Globaler Cooldown: max. 1 Aktion pro Stunde (oder konfigurierbar) unabhängig vom Typ
|
||||
const cooldownMinutes = FalukantService.REPUTATION_ACTION_COOLDOWN_MINUTES;
|
||||
const [{ lastTs }] = await sequelize.query(
|
||||
`
|
||||
SELECT MAX(action_timestamp) AS "lastTs"
|
||||
FROM falukant_log.reputation_action
|
||||
WHERE falukant_user_id = :uid
|
||||
`,
|
||||
{ replacements: { uid: falukantUser.id }, type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
let cooldownRemainingSec = 0;
|
||||
if (lastTs) {
|
||||
const last = new Date(lastTs).getTime();
|
||||
const nextAllowed = last + cooldownMinutes * 60 * 1000;
|
||||
cooldownRemainingSec = Math.max(0, Math.ceil((nextAllowed - Date.now()) / 1000));
|
||||
}
|
||||
|
||||
if (!actionTypes.length) {
|
||||
return {
|
||||
dailyCap,
|
||||
dailyUsed: Number(dailyUsed || 0),
|
||||
dailyRemaining,
|
||||
cooldownMinutes,
|
||||
cooldownRemainingSec,
|
||||
actions: []
|
||||
};
|
||||
}
|
||||
|
||||
// counts in einem Query – aber pro Typ in seinem "Decay-Fenster" (default 7 Tage)
|
||||
const now = Date.now();
|
||||
@@ -3330,7 +3486,14 @@ class FalukantService extends BaseService {
|
||||
currentGain: gain,
|
||||
});
|
||||
}
|
||||
return { dailyCap, dailyUsed: Number(dailyUsed || 0), dailyRemaining, actions };
|
||||
return {
|
||||
dailyCap,
|
||||
dailyUsed: Number(dailyUsed || 0),
|
||||
dailyRemaining,
|
||||
cooldownMinutes,
|
||||
cooldownRemainingSec,
|
||||
actions
|
||||
};
|
||||
}
|
||||
|
||||
async executeReputationAction(hashedUserId, actionTypeId) {
|
||||
@@ -3339,6 +3502,26 @@ class FalukantService extends BaseService {
|
||||
const actionType = await ReputationActionType.findByPk(actionTypeId, { transaction: t });
|
||||
if (!actionType) throw new Error('Unbekannte Aktion');
|
||||
|
||||
// Globaler Cooldown (unabhängig vom Aktionstyp): max. 1 pro Stunde
|
||||
const cooldownMinutes = FalukantService.REPUTATION_ACTION_COOLDOWN_MINUTES;
|
||||
const [{ lastTs }] = await sequelize.query(
|
||||
`
|
||||
SELECT MAX(action_timestamp) AS "lastTs"
|
||||
FROM falukant_log.reputation_action
|
||||
WHERE falukant_user_id = :uid
|
||||
`,
|
||||
{ replacements: { uid: falukantUser.id }, type: sequelize.QueryTypes.SELECT, transaction: t }
|
||||
);
|
||||
if (lastTs) {
|
||||
const last = new Date(lastTs).getTime();
|
||||
const nextAllowed = last + cooldownMinutes * 60 * 1000;
|
||||
const remainingSec = Math.max(0, Math.ceil((nextAllowed - Date.now()) / 1000));
|
||||
if (remainingSec > 0) {
|
||||
const remainingMin = Math.ceil(remainingSec / 60);
|
||||
throw new Error(`Sozialstatus-Aktionen sind nur ${cooldownMinutes} Minutenweise möglich. Bitte warte noch ca. ${remainingMin} Minuten.`);
|
||||
}
|
||||
}
|
||||
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: falukantUser.id },
|
||||
attributes: ['id', 'reputation'],
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"running": "Läuft...",
|
||||
"none": "Keine Aktionen verfügbar.",
|
||||
"dailyLimit": "Heute noch verfügbar: {remaining} / {cap} Reputation (durch Aktionen).",
|
||||
"cooldown": "Nächste Sozialstatus-Aktion in ca. {minutes} Minuten möglich.",
|
||||
"success": "Aktion erfolgreich! Reputation +{gain}, Kosten {cost}.",
|
||||
"successSimple": "Aktion erfolgreich!",
|
||||
"type": {
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
"running": "Running...",
|
||||
"none": "No actions available.",
|
||||
"dailyLimit": "Available today: {remaining} / {cap} reputation (from actions).",
|
||||
"cooldown": "Next social status action available in about {minutes} minutes.",
|
||||
"success": "Action successful! Reputation +{gain}, cost {cost}.",
|
||||
"successSimple": "Action successful!",
|
||||
"type": {
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<p v-if="reputationActionsDailyCap != null" class="reputation-actions-daily">
|
||||
{{ $t('falukant.reputation.actions.dailyLimit', { remaining: reputationActionsDailyRemaining, cap: reputationActionsDailyCap }) }}
|
||||
</p>
|
||||
<p v-if="reputationActionsCooldownRemainingSec > 0" class="reputation-actions-cooldown">
|
||||
{{ $t('falukant.reputation.actions.cooldown', { minutes: Math.ceil(reputationActionsCooldownRemainingSec / 60) }) }}
|
||||
</p>
|
||||
|
||||
<table v-if="reputationActions.length">
|
||||
<thead>
|
||||
@@ -43,7 +46,7 @@
|
||||
<td>+{{ Number(a.currentGain || 0) }}</td>
|
||||
<td>{{ Number(a.timesUsed || 0) }}</td>
|
||||
<td>
|
||||
<button type="button" :disabled="runningActionId === a.id"
|
||||
<button type="button" :disabled="runningActionId === a.id || reputationActionsCooldownRemainingSec > 0"
|
||||
@click.prevent="executeReputationAction(a)">
|
||||
{{ runningActionId === a.id ? $t('falukant.reputation.actions.running') : $t('falukant.reputation.actions.execute') }}
|
||||
</button>
|
||||
@@ -218,6 +221,8 @@ export default {
|
||||
reputationActionsDailyCap: null,
|
||||
reputationActionsDailyUsed: null,
|
||||
reputationActionsDailyRemaining: null,
|
||||
reputationActionsCooldownMinutes: null,
|
||||
reputationActionsCooldownRemainingSec: 0,
|
||||
runningActionId: null,
|
||||
}
|
||||
},
|
||||
@@ -262,6 +267,8 @@ export default {
|
||||
this.reputationActionsDailyCap = data?.dailyCap ?? null;
|
||||
this.reputationActionsDailyUsed = data?.dailyUsed ?? null;
|
||||
this.reputationActionsDailyRemaining = data?.dailyRemaining ?? null;
|
||||
this.reputationActionsCooldownMinutes = data?.cooldownMinutes ?? null;
|
||||
this.reputationActionsCooldownRemainingSec = Number(data?.cooldownRemainingSec ?? 0) || 0;
|
||||
this.reputationActions = Array.isArray(data?.actions) ? data.actions : [];
|
||||
} catch (e) {
|
||||
console.error('Failed to load reputation actions', e);
|
||||
@@ -269,11 +276,14 @@ export default {
|
||||
this.reputationActionsDailyCap = null;
|
||||
this.reputationActionsDailyUsed = null;
|
||||
this.reputationActionsDailyRemaining = null;
|
||||
this.reputationActionsCooldownMinutes = null;
|
||||
this.reputationActionsCooldownRemainingSec = 0;
|
||||
}
|
||||
},
|
||||
async executeReputationAction(action) {
|
||||
if (!action?.id) return;
|
||||
if (this.runningActionId) return;
|
||||
if (this.reputationActionsCooldownRemainingSec > 0) return;
|
||||
this.runningActionId = action.id;
|
||||
try {
|
||||
const { data } = await apiClient.post('/api/falukant/reputation/actions', { actionTypeId: action.id });
|
||||
@@ -409,4 +419,9 @@ table th {
|
||||
margin: 0.5rem 0 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.reputation-actions-cooldown {
|
||||
margin: -0.5rem 0 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user