Skip to content

Instantly share code, notes, and snippets.

@herawais
Created October 5, 2023 10:57
Show Gist options
  • Save herawais/760b8b0e4cf47267a8a130d3d01e9fc1 to your computer and use it in GitHub Desktop.
Save herawais/760b8b0e4cf47267a8a130d3d01e9fc1 to your computer and use it in GitHub Desktop.
src/api /index.ts
//start of APIs for brand-------------------
//GET ALL BRANDS
app.get("/store/brands", cors(storeCorsOptions), async (req, res) => {
const brandService = req.scope.resolve("brandService");
brandService.getBrands().then((brands) => {
return res.json(brands);
});
});
// GET A SINGLE BRAND BY NAME OR ID OR HANDLE
app.get("/store/brand/:param", cors(storeCorsOptions), async (req, res) => {
const brandService = req.scope.resolve("brandService");
const param = req.params.param;
// Try to get the brand by name
brandService.getBrandByName(param).then((brand) => {
if (brand) {
return res.json(brand);
} else {
// If brand not found by name, try to get it by ID
brandService.getBrandById(param).then((brandById) => {
if (brandById) {
return res.json(brandById);
} else {
// If brand not found by ID, try to get it by handle
brandService.getBrandByHandle(param).then((brandByHandle) => {
if (brandByHandle) {
return res.json(brandByHandle);
} else {
// If still not found, return "no record found"
return res.json({ message: "No record found" });
}
});
}
});
}
});
});
// GET ALL BRANDS
app.options("/admin/brands", cors(adminCorsOptions), bodyParser.json());
app.get("/admin/brands", cors(adminCorsOptions), async (req, res) => {
const brandService = req.scope.resolve("brandService");
brandService.getBrands().then((brands) => {
return res.json({ brands });
});
});
// GET A SINGLE BRAND BY ID OR HANDLE ON ADMIN
app.get("/admin/brand/:id", cors(adminCorsOptions), async (req, res) => {
const brandService = req.scope.resolve("brandService");
brandService.getBrandById(req.params.id).then((brand) => {
if (brand) {
return res.json({ brand });
} else {
brandService.getBrandByHandle(req.params.id).then((brandByHandle) => {
return res.json({ brandByHandle });
});
}
});
});
// ADD A BRAND
app.options("/admin/brand", cors(adminCorsOptions), bodyParser.json());
app.post(
"/admin/brand",
cors(adminCorsOptions),
bodyParser.json(),
async (req, res) => {
const schema = z.object({
name: z.string().min(1),
handle: z.string().min(1),
desc: z.string().optional(),
img: z.string().optional(),
});
/* @ts-ignore */
const { success, error, data } = schema.safeParse(req.body);
if (!success) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error);
}
const brandService = req.scope.resolve("brandService");
brandService.addBrand(data).then((brand) => {
return res.json(brand);
});
}
);
// UPDATE A BRAND
app.options("/admin/brand/:id", cors(adminCorsOptions));
app.post(
"/admin/brand/:id",
cors(adminCorsOptions),
bodyParser.json(),
async (req, res) => {
const schema = z.object({
name: z.string().min(1),
handle: z.string().min(1),
desc: z.string().optional(),
img: z.string().optional(),
});
/* @ts-ignore */
const { success, error, data } = schema.safeParse(req.body);
if (!success) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error);
}
const brandService = req.scope.resolve("brandService");
brandService.updateBrand(req.params.id, data).then((brand) => {
return res.json({ brand });
});
}
);
// DELETE A BRAND
app.delete("/admin/brand/:id", cors(adminCorsOptions), async (req, res) => {
const brandService = req.scope.resolve("brandService");
brandService.deleteBrand(req.params.id).then(() => {
return res.sendStatus(200);
});
});
//DELETE A BRAND BY POST METHOD --- because useAdminCustomDelete did not get dynamic id on run time
app.options("/admin/delete-brand/", cors(adminCorsOptions));
app.post(
"/admin/delete-brand/",
cors(adminCorsOptions),
bodyParser.json(),
async (req, res) => {
const schema = z.object({
id: z.string().optional(),
});
/* @ts-ignore */
const { success, error, data } = schema.safeParse(req.body);
if (!success) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error);
}
const brandService = req.scope.resolve("brandService");
brandService.deleteBrand(data.id)
.then(() => {
res.sendStatus(200); // Send a success status code
})
.catch((error) => {
if (error.response && error.response.status === 400) {
res.status(400).json({ error: error.response.data.message }); // Send a custom error response
} else {
res.status(500).json({ error: "An error occurred" }); // Send a generic error response
}
});
})
//end of APIs for brand---------------------
//start of APIs for product-----------------
// UPDATE A BRAND OF PRODUCT
app.options("/admin/brand/product/:id", cors(adminCorsOptions));
app.post(
"/admin/brand/product/:id",
cors(adminCorsOptions),
bodyParser.json(),
async (req, res) => {
const schema = z.object({
brandId: z.string().optional(),
});
/* @ts-ignore */
const { success, error, data } = schema.safeParse(req.body);
if (!success) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error);
}
const productService = req.scope.resolve("productService");
const manager = req.scope.resolve("manager");
await manager.transaction(async (transactionManager) => {
const productServiceTx =
productService.withTransaction(transactionManager);
const product = await productServiceTx.update(req.params.id, {
brand: data.brandId,
});
return res.json({ product });
});
}
);
//end of APIs for product-------------------
@herawais
Copy link
Author

herawais commented Oct 5, 2023

import type { WidgetConfig } from "@medusajs/admin"
import AsyncSelect from 'react-select/async';
import { useAdminCustomQuery, useAdminCustomPost} from 'medusa-react';
import { useState, useEffect } from 'react';
import {useParams} from "react-router-dom";
import {AdminPostProductsProductBrandReq, AdminPostProductsProductBrandRes} from '../types';
import type { SettingProps } from "@medusajs/admin-ui"


const BrandWidget = ({
  notify,
}: SettingProps) => {
  const { id } = useParams(); //id of product
  const [disabled, setDisabled] = useState(true);

  
  const {mutate} = useAdminCustomPost<AdminPostProductsProductBrandReq, AdminPostProductsProductBrandRes>(
      `/admin/brand/product/${id}`,  // path
      ["product-brand-update"], // queryKey - we want to refetch the previous list queries
  )

  const { data: product ,isLoading: isProductloading} = useAdminCustomQuery(
          `/admin/products/${id}`,
          ["get_product"]
  );

  const currentSelectedBrand=product && product?.product ? {label:product?.product?.brand?.name, value:product?.product?.brand?.id}:{label:"No Brand Selected",value:""};

  // State to store the selected value
  const [selectedValue, setSelectedValue] = useState(currentSelectedBrand);
  useEffect(()=>{
      setSelectedValue(currentSelectedBrand);
    },[isProductloading]);

    // getting all brands
  const { data, isLoading } = useAdminCustomQuery(
        "/brands",
        ['get_brands']
      );
      
  const options = data && data.brands ? data.brands.map((brand) => ({
        label: brand.name,
        value: brand.id,
      })) : [];
      
  // Callback function to handle the selected value
  const handleSelectChange = (selectedOption) => {
        if(selectedOption.value!==selectedValue.value){
          setSelectedValue(selectedOption);
          setDisabled(false);
        }
  };

  const filterBrands = (inputValue) => {
        return options.filter((brand) =>{
          if(inputValue.trim() ==""){
            return true;
          }
          else{
            return brand.label.toLowerCase().includes(inputValue.toLowerCase())
          }
        }
        );
      };
    
  const loadBrands = (inputValue, callback) => {
        setTimeout(() => {
          callback(filterBrands(inputValue));
        }, 1000);
      };

  // handel update brand
  const handelBrandUpdate =()=>{
        try {
          const post = {
            brandId:selectedValue.value
        };
        mutate(post, {
            onSuccess: (data) => {
                notify.success("Success","Product's brand Updated Successfully");
                setDisabled(true);
            },
            
        })
        
    }catch (error) {
        notify.error("Error", error);
        console.error('Error update brand:', error);
        }
    }
      
  
  return (
    <div className="bg-white p-8 border border-gray-200 rounded-lg">
      <h1 className="text-grey-90 inter-xlarge-semibold">Brand</h1>
      <div className="pt-base">
      <AsyncSelect
      cacheOptions
      loadOptions={loadBrands}
      value={selectedValue}
      onChange={handleSelectChange}
    />
      </div>
      <div className="pt-base flex items-center justify-end gap-x-6">
        <button 
            className={`rounded-md ${disabled?"bg-indigo-400":"bg-indigo-600"} px-3 py-2 text-sm font-semibold text-white shadow-sm ${disabled?"":"hover:bg-indigo-500"} focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600`}
            onClick={handelBrandUpdate}
            disabled={disabled}
            >
            Update
        </button>
      </div>
    </div>
  );
}

export const config: WidgetConfig = {
  zone: "product.details.after",
}

export default BrandWidget;

@herawais
Copy link
Author

herawais commented Oct 5, 2023

import { BaseEntity} from "@medusajs/medusa"
import { generateEntityId } from "@medusajs/utils"
import { BeforeInsert, Column, Entity, Index, OneToMany } from "typeorm"
import {Product} from "./product";


@Entity()
export class Brand extends BaseEntity {

   @Index({ unique: true })
   @Column({ type: "varchar", nullable: false })
   name: string
   
   @Index({ unique: true })
   @Column({ type: "varchar", nullable: true })
   handle: string
   
   @Column({ type: "varchar", nullable: true })
   desc: string

   @Column({ type: "varchar", nullable: true })
   img: string | null


   @BeforeInsert()
   private beforeInsert(): void {
      this.id = generateEntityId(this.id, "brand")
   }

   @OneToMany(() => Product, (product)=>product.brand)
   product: Product[]
}

@herawais
Copy link
Author

herawais commented Oct 5, 2023

import { Brand } from "../models/brand"
import { dataSource } from '@medusajs/medusa/dist/loaders/database'

export const BrandRepository = dataSource.getRepository(Brand)

@herawais
Copy link
Author

herawais commented Oct 5, 2023

import { TransactionBaseService } from '@medusajs/medusa'
import { BrandRepository } from 'repositories/brand'

export default class BrandService extends TransactionBaseService {

	protected readonly brandRepository_: typeof BrandRepository

	constructor({ brandRepository }) {
		super(arguments[0])
		this.brandRepository_ = brandRepository
	}

	async getBrands() {
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		return await brandRepository.find()
	}	

	async getBrandById(id) {
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		return await brandRepository.findOne({
			where: { id }
		})
	}

	async getBrandByName(name) {
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		return await brandRepository.findOne({
			where: { name }
		})
	}
    
	async getBrandByHandle(handle) {
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		return await brandRepository.findOne({
			where: { handle }
		})
	}

	async addBrand(post) {
		const { name, handle, desc, img } = post
		if (!name) throw new Error("Adding a brand requires a unique name")
		
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		const createdBrand = brandRepository.create({
			name,
			handle,
            desc,
            img
		})
		const brand = await brandRepository.save(createdBrand)
		return brand
	}
	
	async updateBrand(id, post) {
		const { name, handle, desc, img } = post
		if (!id || !name || !handle) throw new Error("Updating a brand requires an id, a unique name, and a unique handle")
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		const existingBrand = await brandRepository.findOne({ 
			where: { id } 
		})
		if (!existingBrand) throw new Error("No Brand found with that id")
		existingBrand.name = name
		existingBrand.handle = handle
		existingBrand.desc = desc
		existingBrand.img = img
		const brand = await brandRepository.save(existingBrand)
		return brand
	}

	async deleteBrand(id) {
		if (!id) throw new Error("Deleting a brand requires an id")
		/* @ts-ignore */
		const brandRepository = this.activeManager_.withRepository(this.brandRepository_)
		try {
			const deletedCount = (await brandRepository.delete(id)).affected;			
			return { success: true };
		  } catch (error) {
			if (error.code === "23503") {
			  const response = {
				status: 400,
				message: error.message,
			  };
			  throw response;
			} else {
			  throw {
				status: 500,
				message: "An error occurred",
			  };
			}
		  }
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment