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

fix(RSS-ECOMM-5_26): lock buttons for response #377

Merged
merged 4 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Project Configuration
VITE_APP_CTP_PROJECT_KEY=your-project-key
VITE_APP_CTP_CLIENT_SECRET=your-client-secret
VITE_APP_CTP_CLIENT_ID=your-client-id
VITE_APP_CTP_REGION=europe-west1
VITE_APP_CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com/
VITE_APP_CTP_API_URL=https://api.europe-west1.gcp.commercetools.com/
VITE_APP_CTP_SCOPES=manage_project:your-project-key view_audit_log:your-project-key manage_api_clients:your-project-key view_api_clients:your-project-key

# Application Configuration
VITE_APP_DEFAULT_SEGMENT='/'
VITE_APP_NEXT_SEGMENT=1
VITE_APP_PATH_SEGMENTS_TO_KEEP=0
VITE_APP_PROJECT_TITLE=Greenshop
VITE_APP_SEARCH_SEGMENT='?'
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Welcome to [Greenshop](https://mad-wizards-greenshop.netlify.app/), your digital
- [Key Features](#key-features-of-greenshop-include-%EF%B8%8F) πŸ—οΈ
- [Technical Stack](#technical-stack-) πŸ’»
- [How to Run the Project Locally](#how-to-run-the-project-locally-%EF%B8%8F) βš™οΈ
- [Availabile Scripts](#availabile-scripts-) πŸ“‘
- [Available Scripts](#available-scripts-) πŸ“‘
- [Contact us](#contact-us-) πŸ“©

### Our Mission 🌸
Expand All @@ -21,25 +21,27 @@ Our modern and minimalist website offers a sleek and intuitive shopping experien

πŸ”Ž **Comprehensive Product Selection**: Explore our extensive catalog of potted plants, seeds, soil, and gardening essentials, handpicked to ensure the highest quality and variety.

🎨 **Seamless UI/UX**: Enjoy a cohesive and engaging user experience with a beautifully crafted interface that prioritizes ease of use and accessibility.

🧭 **User-friendly Navigation**: Our intuitive navigation system makes it easy for you to find the plants, seeds, soil, and accessories you need.

🧩 **Elegance and Functionality**: Our intuitive design and intuitive features make it easy to shop for plants, seeds, soil, and accessories.
🧩 **Elegance and Functionality**: Our intuitive design and thoughtful features make it easy to shop for plants, seeds, soil, and accessories.

πŸ–ΌοΈ **Responsive Design**: Whether you're browsing on a desktop, tablet, or smartphone, our website adapts seamlessly to provide a visually stunning and immersive experience on any device.

πŸ” **Secure checkout process**: Secure checkout ensures you can shop with confidence and peace of mind.

## Technical Stack πŸ’»

_in our project we used the following technologies:_

- **Frontend**: Utilizes [HTML](https://www.w3schools.com/html/), [SASS](https://sass-lang.com/), and [Typescript](https://www.typescriptlang.org/) to craft a dynamic and engaging user interface 🎨
- **Bundling**: Employs [Vite](https://vitejs.dev/) as the bundler, ensuring swift development server startup time and seamless module replacement 🌳
- **CI/CD**: Integrates [GitHub Actions](https://github.com/features/actions), [Plop](https://plopjs.com/), [Netlify](https://www.netlify.com/) for continuous integration and deployment πŸš€
- **Frontend**: Utilizes [Typescript](https://www.typescriptlang.org/), [HTML](https://www.w3schools.com/html/), [SASS](https://sass-lang.com/), and [modern-normalize](https://github.com/sindresorhus/modern-normalize) to craft a dynamic and engaging user interface 🎨
- **Backend**: Supported by [CommerceTools](https://commercetools.com/), a leading provider of commerce solutions, offering a robust and scalable platform for creating immersive digital commerce experiences 🌐
- **CI/CD**: Integrates [GitHub Actions](https://github.com/features/actions) and [Netlify](https://www.netlify.com/) for continuous integration and deployment πŸš€
- **Deployment**: Hosted on [Netlify](https://www.netlify.com/), enabling efficient and hassle-free deployment of the application 🌟
- **Code Quality**: Ensured code quality through rigorous checks by [Husky](https://typicode.github.io/husky/), [Prettier](https://prettier.io/), [ESLint](https://eslint.org/), [Perfectionist](https://eslint-plugin-perfectionist.azat.io/), [Stylelint](https://stylelint.io/), [SonarLint](https://www.sonarsource.com/products/sonarlint/), and [EditorConfig](https://editorconfig.org/), maintaining consistency and best practices throughout the codebase 🐢
- **Testing**: Thorough testing conducted with [Vitest](https://vitest.dev/), ensuring the reliability and robustness of the application's functionalities ⚑
- **Backend**: Supported by [CommerceTools](https://commercetools.com/), a leading provider of commerce solutions, offering a robust and scalable platform for creating immersive digital commerce experiences 🌐
- **Testing**: Thorough testing conducted with [Vitest](https://vitest.dev/), [Mock Service Worker](https://mswjs.io/), and [Sinon.js](https://sinonjs.org/), ensuring the reliability and robustness of the application's functionalities ⚑
- **Bundling**: Employs [Vite](https://vitejs.dev/) as the bundler, ensuring swift development server startup time and seamless module replacement 🌳
- **Additional Features and Libraries**: [Plop](https://plopjs.com/) for generating components from a template, [Hammer.js](https://hammerjs.github.io/) for touch interactions, [js-cookie](https://github.com/js-cookie/js-cookie) for cookie management, [noUiSlider](https://github.com/leongersen/noUiSlider) for range sliders, [Swiper](https://swiperjs.com/) for image carousel, and [postcode-validator](https://www.npmjs.com/package/postcode-validator) for address validation πŸ“¦
- **Project's Architecture**: Carefully designed and implemented [Feature-Sliced Design](https://feature-sliced.design/) for efficient, scalable, and maintainable development 🌍

## How to Run the Project Locally βš™οΈ

Expand All @@ -50,7 +52,7 @@ _to run the project locally, you can follow the following steps:_
- Install dependencies: `npm install`
- Run the project: `npm run dev`

## Availabile Scripts πŸ“‘
## Available Scripts πŸ“‘

_you can run the following scripts in the project directory:_

Expand Down
11 changes: 8 additions & 3 deletions src/entities/Coupon/view/CouponView.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DeleteCallback } from '@/entities/Summary/model/SummaryModel';
import type { CartCoupon } from '@/shared/types/cart';

import ButtonModel from '@/shared/Button/model/ButtonModel.ts';
import createBaseElement from '@/shared/utils/createBaseElement.ts';
import { minusCartPrice } from '@/shared/utils/messageTemplates.ts';

Expand Down Expand Up @@ -35,9 +36,13 @@ class CouponView {
});

const couponWrap = createBaseElement({ cssClasses: [styles.coupon], tag: 'div' });
const deleteCoupon = createBaseElement({ cssClasses: [styles.deleteCoupon], tag: 'button' });
deleteCoupon.addEventListener('click', () => this.deleteCallback(this.coupon.coupon));
couponWrap.append(deleteCoupon, couponTitle);
const deleteCoupon = new ButtonModel({ classes: [styles.deleteCoupon] });
deleteCoupon.getHTML().addEventListener('click', async () => {
deleteCoupon.setDisabled();
await this.deleteCallback(this.coupon.coupon);
deleteCoupon.setEnabled();
});
couponWrap.append(deleteCoupon.getHTML(), couponTitle);
this.view.append(couponWrap, this.couponValue);

return this.view;
Expand Down
12 changes: 9 additions & 3 deletions src/entities/ProductCard/model/ProductCardModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { PAGE_ID } from '@/shared/constants/pages.ts';
import { SEARCH_PARAMS_FIELD } from '@/shared/constants/product.ts';
import * as buildPath from '@/shared/utils/buildPathname.ts';
import getLanguageValue from '@/shared/utils/getLanguageValue.ts';
import { productAddedToCartMessage } from '@/shared/utils/messageTemplates.ts';
import { productAddedToCartMessage, productNotAddedToCartMessage } from '@/shared/utils/messageTemplates.ts';
import { showErrorMessage, showSuccessMessage } from '@/shared/utils/userMessage.ts';
import ProductInfoModel from '@/widgets/ProductInfo/model/ProductInfoModel.ts';

Expand Down Expand Up @@ -48,7 +48,10 @@ class ProductCardModel {
showSuccessMessage(productAddedToCartMessage(this.getProductMeta().name));
this.view.getAddToCartButton().setDisabled();
})
.catch(showErrorMessage);
.catch(() => {
showErrorMessage(productNotAddedToCartMessage(this.getProductMeta().name));
this.view.getAddToCartButton().setEnabled();
});
}

private getProductMeta(): AddCartItem {
Expand Down Expand Up @@ -97,7 +100,10 @@ class ProductCardModel {
this.view
.getAddToCartButton()
.getHTML()
.addEventListener('click', () => this.addProductToCartHandler());
.addEventListener('click', () => {
this.view.getAddToCartButton().setDisabled();
this.addProductToCartHandler();
});
}

private setCardHandler(): void {
Expand Down
11 changes: 6 additions & 5 deletions src/features/AddressAdd/model/AddressAddModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ class AddressAddModel {
if (this.shouldSetDefaultAddress()) {
actions.push(this.getDefaultAddressAction(newAddress.id));
}

await getCustomerModel().editCustomer(actions, updatedUser);
this.handleSuccess();
}
Expand Down Expand Up @@ -74,7 +73,6 @@ class AddressAddModel {
private async createNewAddress(): Promise<void> {
const loader = new LoaderModel(LOADER_SIZE.SMALL).getHTML();
this.view.getSaveChangesButton().getHTML().append(loader);

try {
const user = await getCustomerModel().getCurrentUser();
if (user) {
Expand Down Expand Up @@ -147,7 +145,6 @@ class AddressAddModel {
const cancelButton = this.view.getCancelButton().getHTML();
cancelButton.addEventListener('click', () => {
modal.hide();
modal.removeContent();
});
return true;
}
Expand All @@ -164,8 +161,12 @@ class AddressAddModel {
}

private setSubmitFormHandler(): boolean {
const submitButton = this.view.getSaveChangesButton().getHTML();
submitButton.addEventListener('click', () => this.createNewAddress());
const submitButton = this.view.getSaveChangesButton();
submitButton.getHTML().addEventListener('click', async () => {
submitButton.setDisabled();
await this.createNewAddress();
submitButton.setEnabled();
});
return true;
}

Expand Down
9 changes: 6 additions & 3 deletions src/features/AddressEdit/model/AddressEditModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ class AddressEditModel {
const cancelButton = this.view.getCancelButton().getHTML();
cancelButton.addEventListener('click', () => {
modal.hide();
modal.removeContent();
});
return true;
}
Expand All @@ -130,8 +129,12 @@ class AddressEditModel {
}

private setSubmitFormHandler(): boolean {
const submitButton = this.view.getSaveChangesButton().getHTML();
submitButton.addEventListener('click', () => this.editAddressInfo());
const submitButton = this.view.getSaveChangesButton();
submitButton.getHTML().addEventListener('click', async () => {
submitButton.setDisabled();
await this.editAddressInfo();
submitButton.setEnabled();
});
return true;
}

Expand Down
45 changes: 26 additions & 19 deletions src/features/PasswordEdit/model/PasswordEditModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ class PasswordEditModel {
this.init();
}

private clearInputFields(): void {
this.view.getInputFields().forEach((inputField) => {
inputField.getView().getInput().setValue('');
const errorField = inputField.getView().getErrorField();
if (errorField?.textContent) {
errorField.textContent = '';
}
});
}

private init(): void {
this.view.getInputFields().forEach((inputField) => this.setInputFieldHandlers(inputField));
this.setPreventDefaultToForm();
Expand All @@ -26,7 +36,7 @@ class PasswordEditModel {
this.setCancelButtonHandler();
}

private async saveNewPassword(): Promise<boolean> {
private async saveNewPassword(): Promise<void> {
const loader = new LoaderModel(LOADER_SIZE.SMALL).getHTML();
this.view.getSubmitButton().getHTML().append(loader);
try {
Expand All @@ -41,7 +51,7 @@ class PasswordEditModel {
this.view.getNewPasswordField().getView().getValue(),
);
showSuccessMessage(SERVER_MESSAGE_KEY.PASSWORD_CHANGED);
modal.hide();
this.clearInputFields();
} catch {
showErrorMessage(SERVER_MESSAGE_KEY.PASSWORD_NOT_CHANGED);
}
Expand All @@ -51,64 +61,61 @@ class PasswordEditModel {
showErrorMessage(error);
} finally {
loader.remove();
modal.hide();
}
return true;
}

private setCancelButtonHandler(): boolean {
private setCancelButtonHandler(): void {
this.view
.getCancelButton()
.getHTML()
.addEventListener('click', () => {
this.clearInputFields();
modal.hide();
});
return true;
}

private setInputFieldHandlers(inputField: InputFieldModel): boolean {
private setInputFieldHandlers(inputField: InputFieldModel): void {
const inputHTML = inputField.getView().getInput().getHTML();
inputHTML.addEventListener('input', () => this.switchSubmitFormButtonAccess());
return true;
}

private setPreventDefaultToForm(): boolean {
private setPreventDefaultToForm(): void {
this.view.getHTML().addEventListener('submit', (event) => event.preventDefault());
return true;
}

private setSubmitFormHandler(): boolean {
const submitButton = this.view.getSubmitButton().getHTML();
submitButton.addEventListener('click', this.saveNewPassword.bind(this));
return true;
private setSubmitFormHandler(): void {
const submitButton = this.view.getSubmitButton();
submitButton.getHTML().addEventListener('click', async () => {
submitButton.setDisabled();
await this.saveNewPassword();
});
}

private setSwitchNewPasswordVisibilityHandler(): boolean {
private setSwitchNewPasswordVisibilityHandler(): void {
this.view.getShowNewPasswordElement().addEventListener('click', () => {
const input = this.view.getNewPasswordField().getView().getInput().getHTML();
input.type = input.type === INPUT_TYPE.PASSWORD ? INPUT_TYPE.TEXT : INPUT_TYPE.PASSWORD;
input.placeholder = input.type === INPUT_TYPE.PASSWORD ? PASSWORD_TEXT.HIDDEN : PASSWORD_TEXT.SHOWN;
this.view.switchPasswordElementSVG(input.type, this.view.getShowNewPasswordElement());
});
return true;
}

private setSwitchOldPasswordVisibilityHandler(): boolean {
private setSwitchOldPasswordVisibilityHandler(): void {
this.view.getShowOldPasswordElement().addEventListener('click', () => {
const input = this.view.getOldPasswordField().getView().getInput().getHTML();
input.type = input.type === INPUT_TYPE.PASSWORD ? INPUT_TYPE.TEXT : INPUT_TYPE.PASSWORD;
input.placeholder = input.type === INPUT_TYPE.PASSWORD ? PASSWORD_TEXT.HIDDEN : PASSWORD_TEXT.SHOWN;
this.view.switchPasswordElementSVG(input.type, this.view.getShowOldPasswordElement());
});
return true;
}

private switchSubmitFormButtonAccess(): boolean {
private switchSubmitFormButtonAccess(): void {
if (this.view.getInputFields().every((inputField) => inputField.getIsValid())) {
this.view.getSubmitButton().setEnabled();
} else {
this.view.getSubmitButton().setDisabled();
}
return true;
}

public getHTML(): HTMLFormElement {
Expand Down
13 changes: 3 additions & 10 deletions src/features/PasswordEdit/view/PasswordEditView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class PasswordEditView {
constructor() {
this.submitButton = this.createSubmitButton();
this.cancelButton = this.createCancelButton();
this.showOldPasswordElement = this.createShowOldPasswordElement();
this.showNewPasswordElement = this.createShowNewPasswordElement();
this.showOldPasswordElement = this.createShowPasswordElement();
this.showNewPasswordElement = this.createShowPasswordElement();
this.oldPasswordField = this.createOldPasswordField();
this.newPasswordField = this.createNewPasswordField();
this.view = this.createHTML();
Expand Down Expand Up @@ -93,14 +93,7 @@ class PasswordEditView {
return this.oldPasswordField;
}

private createShowNewPasswordElement(): HTMLDivElement {
return createBaseElement({
cssClasses: [styles.showPasswordElement],
tag: 'div',
});
}

private createShowOldPasswordElement(): HTMLDivElement {
private createShowPasswordElement(): HTMLDivElement {
return createBaseElement({
cssClasses: [styles.showPasswordElement],
tag: 'div',
Expand Down
9 changes: 6 additions & 3 deletions src/features/PersonalInfoEdit/model/PersonalInfoEditModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ class PersonalInfoEditModel {
],
user,
);
modal.hide();
EventMediatorModel.getInstance().notify(MEDIATOR_EVENT.REDRAW_USER_INFO, '');
showSuccessMessage(SERVER_MESSAGE_KEY.PERSONAL_INFO_CHANGED);
}
} catch (error) {
showErrorMessage(error);
} finally {
loader.remove();
modal.hide();
}
}

Expand Down Expand Up @@ -111,8 +111,11 @@ class PersonalInfoEditModel {
}

private setSubmitFormHandler(): boolean {
const submitButton = this.view.getSaveChangesButton().getHTML();
submitButton.addEventListener('click', () => this.editPersonalInfo());
const submitButton = this.view.getSaveChangesButton();
submitButton.getHTML().addEventListener('click', async () => {
submitButton.setDisabled();
await this.editPersonalInfo();
});
return true;
}

Expand Down
Loading