Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/let user remove custom exchange rate #195

Merged
merged 10 commits into from
Jul 14, 2023
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module.exports = {
},
],

ignorePatterns: ['cypress/**/*', 'cypress.config.ts'],
ignorePatterns: ['cypress/**/*', 'cypress.config.ts', 'backend'],

extends: [
'plugin:vue/vue3-essential',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build": "vue-tsc --noEmit && vite build",
"lint": "eslint -c ./.eslintrc.js --ignore-path ./.eslintignore",
"lint": "eslint -c ./.eslintrc.js --ignore-path ./.eslintignore .",
"dev": "vite",
"prod": "vite --mode production",
"docker-build": "docker build . -t letehaha/budget-tracker-fe",
Expand Down
7 changes: 7 additions & 0 deletions src/api/currencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const loadUserCurrencies = async () => {
return result.map(item => new UserCurrencyRecord(item));
};

export const deleteCustomRate = (
pairs: {
baseCode: string;
quoteCode: string;
}[],
) => api.delete('/user/currency/rates', { pairs });

export const loadUserCurrenciesExchangeRates = async () => {
const result = await api.get('/user/currencies/rates');

Expand Down
10 changes: 9 additions & 1 deletion src/components/fields/input-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div
:class="{
'input-field--error': errorMessage,
'input-field--disabled': $attrs.disabled
'input-field--disabled': disabled,
}"
class="input-field"
>
Expand All @@ -18,6 +18,7 @@
:type="type"
:value="modelValue"
:style="inputFieldStyles"
:disabled="disabled"
:tabindex="tabindex"
:min="minValue"
class="input-field__input"
Expand Down Expand Up @@ -62,6 +63,7 @@ export default defineComponent({
label: { type: String, default: undefined },
modelValue: { type: [String, Number], default: undefined },
type: { type: String, default: undefined },
disabled: { type: Boolean, default: false },
tabindex: { type: String, default: undefined },
errorMessage: { type: String, default: undefined },
inputFieldStyles: { type: Object, default: undefined },
Expand All @@ -78,12 +80,15 @@ export default defineComponent({
onInput: (event: InputChangeEvent) => {
let value: string | number = event.target.value;

if (props.disabled) return;
if (props.modelValue === value) return;
if (props.type === 'number') value = Number(value);

emit(MODEL_EVENTS.input, value);
},
onkeypress: (event: KeyboardEvent) => {
if (props.disabled) return;

if (props.type === 'number') {
if (event.keyCode === KEYBOARD_CODES.keyE) {
event.preventDefault();
Expand Down Expand Up @@ -131,6 +136,9 @@ export default defineComponent({
width: 100%;
flex: 1;
}
.input-field--disabled {
opacity: 0.6;
}
.input-field__input {
font-size: 16px;
line-height: 1;
Expand Down
4 changes: 4 additions & 0 deletions src/js/records/models/exchange-rate.record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ interface Record {
baseId: number;
quoteId: number;
rate: number;
custom: boolean;
baseCode: string;
quoteCode: string;
date: string;
Expand All @@ -21,6 +22,8 @@ export class ExchangeRateRecord {

quoteRate: number;

custom: boolean;

baseCode: string;

quoteCode: string;
Expand All @@ -33,6 +36,7 @@ export class ExchangeRateRecord {
this.id = record.id;
this.baseId = record.baseId;
this.quoteId = record.quoteId;
this.custom = record.custom;
this.rate = record.rate;
this.quoteRate = Number(Number(1 / record.rate).toFixed(5));
this.baseCode = record.baseCode;
Expand Down
77 changes: 72 additions & 5 deletions src/pages/settings/tabs/currencies/edit-currency.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@
<input-field
v-model="form.baseRate"
:label="`1 ${currency.code} =`"
:custom-disabled="!isChecked"
@focus="onBaseFocus"
/>
<input-field
v-model="form.quoteRate"
:label="`1 ${currency.quoteCode} =`"
:custom-disabled="!isChecked"
@focus="onQuoteFocus"
/>
<div class="edit-currency__checkbox">
<label class="edit-currency__label">
<span class="edit-currency__live-span">Live update</span>
<input
:checked="!currency.custom"
class="tick-field__input"
type="checkbox"
@change="toggleChange"
>
</label>
</div>
</div>
<div class="edit-currency__actions">
<ui-tooltip :content="!isFormDirty ? 'Nothing to save' : ''">
Expand All @@ -28,7 +41,7 @@
:disabled="deletionDisabled"
@click="onDeleteHandler"
>
Delete
Delete currency
</ui-button>
</ui-tooltip>
</div>
Expand All @@ -40,14 +53,15 @@ import {
defineComponent,
reactive,
computed,
onMounted,
ref,
watch,
PropType,
} from 'vue';
import { API_ERROR_CODES } from 'shared-types';
import { storeToRefs } from 'pinia';
import { useCurrenciesStore } from '@/stores';
import { editUserCurrenciesExchangeRates } from '@/api/currencies';
import { editUserCurrenciesExchangeRates, deleteCustomRate } from '@/api/currencies';
import UiButton, { BUTTON_THEMES } from '@/components/common/ui-button.vue';
import InputField from '@/components/fields/input-field.vue';
import UiTooltip from '@/components/common/tooltip.vue';
Expand Down Expand Up @@ -89,13 +103,17 @@ export default defineComponent({
});
const isBaseEditing = ref(false);
const isQuoteEditing = ref(false);
const isChecked = ref<boolean>(false);

const isRateChanged = computed(() => (
+props.currency.rate !== +form.baseRate
|| +props.currency.quoteRate !== +form.quoteRate
));

const isFormDirty = computed(() => isRateChanged.value);
const isFormDirty = computed(() => (
isRateChanged.value
|| (props.currency.custom && !isChecked.value)
));

const onBaseFocus = () => {
isBaseEditing.value = true;
Expand All @@ -105,6 +123,9 @@ export default defineComponent({
isQuoteEditing.value = true;
isBaseEditing.value = false;
};
const toggleChange = (event) => {
isChecked.value = !event.target.checked;
};

watch(
() => form.baseRate,
Expand All @@ -123,10 +144,39 @@ export default defineComponent({
},
);

onMounted(() => {
isChecked.value = props.currency.custom;
});

const onDeleteHandler = () => {
emit('delete');
};

const deleteExchangeRates = async () => {
try {
await deleteCustomRate([
{
baseCode: props.currency.code,
quoteCode: props.currency.quoteCode,
},
{
baseCode: props.currency.quoteCode,
quoteCode: props.currency.code,
},
]);

emit('submit');

addSuccessNotification('Successfully updated.');
} catch (e) {
if (e.data.code === API_ERROR_CODES.validationError) {
addErrorNotification(e.data.message);
return;
}
addErrorNotification('Unexpected error');
}
};

const updateExchangeRates = async () => {
try {
await editUserCurrenciesExchangeRates([
Expand Down Expand Up @@ -156,14 +206,18 @@ export default defineComponent({
};

const onSaveHandler = async () => {
if (isRateChanged.value) {
if (isRateChanged.value && !props.currency.custom) {
await updateExchangeRates();
} else if (props.currency.custom) {
await deleteExchangeRates();
}
};

return {
DISABLED_DELETE_TEXT,
BUTTON_THEMES,
isChecked,
toggleChange,
onSaveHandler,
onDeleteHandler,
form,
Expand All @@ -190,8 +244,21 @@ export default defineComponent({
gap: 32px;
margin-top: 32px;
}
.edit-currency__checkbox {
display: flex;
justify-content: center;
align-items: center;
}
.edit-currency__label {
display: flex;
justify-content: center;
align-items: center;
}
.edit-currency__live-span {
margin-right: 10px;
}
.edit-currency__ratio {
max-width: 360px;
max-width: 485px;
display: flex;
gap: 16px;
}
Expand Down
4 changes: 3 additions & 1 deletion src/pages/settings/tabs/currencies/list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default defineComponent({
return {
...item,
rate: Number(rate?.rate?.toFixed(4)),
custom: rate?.custom ?? false,
quoteCode: rate?.quoteCode,
quoteRate: Number(rate?.quoteRate?.toFixed(4)),
};
Expand Down Expand Up @@ -147,6 +148,7 @@ export default defineComponent({

const onSubmitHandler = () => {
loadRates();
toggleActiveItem(null);
};

const isDeletionDisabled = (currency: UserCurrencyRecord) => (
Expand Down Expand Up @@ -184,7 +186,7 @@ export default defineComponent({
padding: var(--settings-currency-list-item-padding);

display: grid;
grid-template-columns: 50px 300px 1fr;
grid-template-columns: 50px 300px 1fr 50px;
grid-gap: 16px;

transition: background-color .2s ease-out;
Expand Down
1 change: 1 addition & 0 deletions src/pages/settings/tabs/currencies/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { UserCurrencyRecord } from '@/js/records';
export type CurrencyWithExchangeRate = UserCurrencyRecord & {
rate: number;
quoteCode: string;
custom?: boolean;
quoteRate: number;
}