Domaca Hrana Shop

Domaca Hrana Shop: We Are What We Eat

By Jasmin Ibrišimbegović

Introduction

I’m continuing my series on ethical software projects—tools designed not just to function, but to help society live in a healthier, more intentional way.

In my previous article, I introduced We The People, a platform implementing Liquid Democracy. Now, I’d like to present a project with a different mission: connecting local farmers directly with buyers and promoting healthy, organic food—because we truly are what we eat.

Project Intro

Project Vision

Domaca Hrana is a sales and delivery platform for locally produced food. Producers can list their goods, and buyers can easily order fresh products from nearby sources. The platform runs on both web and mobile, with advanced order tracking and delivery features.

Unlike many projects that start with a bare-minimum MVP, this platform launched as a fully developed system with all essential modules. In terms of functionality, I can confidently say Domaca Hrana Shop rivals platforms like OLX.ba—and in fact, it operates as six distinct apps.

Platform Components

  • Web App (Angular 16): Responsive SPA optimized for desktop and mobile browsers.
  • Backend API (Laravel 10, PHP 8.2+): REST API with JWT authentication and MySQL database. Scope: 120 endpoints—the largest solo API I’ve built to date.
  • Admin Panel (Angular 16): Manage users, content, and platform settings.
  • User Mobile App (Ionic 7, Angular 16): Web + mobile with Firebase push notifications and Capacitor plugins for geolocation and camera.
  • Store Mobile App (Ionic 7, Angular 16): Enables store management with native device features.
  • Driver App: Uber-style delivery management for fresh, organic food.

AI Integrations

The platform leverages AI vision models for automatic product classification, enabling store owners to populate inventories quickly and efficiently.

Reflections on Tech Stack

This was my first project with Laravel, and it exceeded expectations. The framework’s depth and structure made the API work both challenging and rewarding.

Ionic also proved invaluable. While it runs in a WebView and doesn’t expose every native feature, it’s perfect for e-commerce. Its biggest strength is that it deepens your knowledge of Angular/React while speeding up cross-platform development.

CI/CD was implemented via GitHub Actions, so deployments to production were literally one click. Publishing to the Google Play Store was more delicate, as their review bot is strict, but our apps passed on the first attempt thanks to careful preparation.

The real challenge—and excitement—was building 4–5 apps in parallel, ensuring uniformity in branding and design while balancing technical depth.

App Demos

Explore the different apps of Domaca Hrana Shop. Test accounts are provided for demo purposes.

User App Icon User Web App
domacahrana.shop/home
Login: info@infinityapps.ai / Password: Test123!
User App Icon User Mobile App
Google Play Store
Store App Icon Store Web App
storeapp.domacahrana.shop
Login: jaskobh@hotmail.com / Password: Test123!
Store App Icon Store Mobile App
Google Play Store
Store App Screenshot 1 Store App Screenshot 2 Store App Screenshot 3
Admin Icon Admin Panel
admin.domacahrana.shop
Login: domacahranashop@gmail.com / Password: Test123!
Admin Panel Screenshot 1 Admin Panel Screenshot 2

Code Highlights

Here’s a sample from the Chat-Inbox module written in TypeScript:


import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { IonContent, NavController } from '@ionic/angular';
import { ApiService } from 'src/app/services/api.service';
import { UtilService } from 'src/app/services/util.service';

@Component({
  selector: 'app-inbox',
  templateUrl: './inbox.page.html',
  styleUrls: ['./inbox.page.scss'],
})
export class InboxPage implements OnInit {
  @ViewChild(IonContent, { read: IonContent, static: false }) myContent: IonContent;
  receiver_id: any;
  roomId: any;
  message: any;
  messageList: any;
  loaded: boolean;
  yourMessage: boolean;
  interval: any;
  uid: any;
  name: any = '';
  private previousMessageCount: number = 0;
  constructor(
    private route: ActivatedRoute,
    public api: ApiService,
    public util: UtilService,
    private navCtrl: NavController
  ) {
    this.route.queryParams.subscribe((data: any) => {
      console.log(data);
      if (data && data.id) {
        this.loaded = false;
        this.receiver_id = data.id;
        this.uid = localStorage.getItem('uid')
        this.yourMessage = true;
        this.name = data.name;
        this.getChatRooms();
      }
    })
  }

  getChatRooms(event?: any) {
    const param = {
      uid: this.uid,
      participants: this.receiver_id
    };
    this.api.post_private('v1/chats/getChatRooms', param).then((data: any) => {
      console.log(data);
      if (event) {
        event.target.complete();
      }
      if (data && data.status && data.status == 200) {
        if (data && data.data && data.data.id) {
          this.roomId = data.data.id;
        } else if (data && data.data2 && data.data2.id) {
          this.roomId = data.data2.id;
        }

        this.loaded = true;
        this.getChatsList();
        this.interval = setInterval(() => {
          console.log('calling in interval');
          this.getChatsList();
        }, 12000);
      } else {
        this.createChatRooms();
      }
    }, error => {
      console.log('error', error);
      this.loaded = false;
      this.createChatRooms();
    }).catch(error => {
      this.loaded = false;
      this.createChatRooms();
      console.log('error', error);
    });
  }

  createChatRooms() {
    const param = {
      uid: this.uid,
      participants: this.receiver_id,
      status: 1
    };
    this.api.post_private('v1/chats/createChatRooms', param).then((data: any) => {
      console.log(data);
      this.loaded = true;

      if (data && data.status && data.status == 200 && data.data) {
        this.roomId = data.data.id;
        this.getChatsList();
        this.interval = setInterval(() => {
          console.log('calling in interval');
          this.getChatsList();
        }, 12000);
      }
    }, error => {
      console.log('error', error);
      this.loaded = true;
    }).catch(error => {
      this.loaded = true;
      console.log('error', error);
    });
  }


  getChatsList() {
    this.api.post_private('v1/chats/getById', { room_id: this.roomId }).then((data: any) => {
      console.log(data);
      if (data && data.status && data.status == 200 && data.data.length) {
        this.messageList = data.data;
        const isNewMessage = data.data.length > this.previousMessageCount;
        this.previousMessageCount = data.data.length;
        if (isNewMessage) {
          this.scrollToBottom();
        }
      }
    }, error => {
      console.log(error);
    }).catch(error => {
      console.log(error);
    });
  }

  ngOnInit() {
  }

  ionViewDidLeave() {
    console.log('leaae');
    clearInterval(this.interval);
  }

  sendMessage() {
    console.log(this.message);
    if (!this.message || this.message == '') {
      return false;
    }
    const msg = this.message;
    this.message = '';
    const param = {
      room_id: this.roomId,
      uid: this.receiver_id, // Set to the recipient's ID
      from_id: this.uid, // Set to the sender's ID
      message: msg,
      message_type: 0,
      status: 1,
    };
    this.yourMessage = false;
    this.api.post_private('v1/chats/sendMessage', param).then((data: any) => {
      console.log(data);
      this.yourMessage = true;

      if (data && data.status == 200) {
        this.getChatsList();
      } else {
        this.yourMessage = true;
      }
    }, error => {
      console.log(error);
      this.yourMessage = true;
      this.util.apiErrorHandler(error);
    }).catch(error => {
      console.log(error);
      this.yourMessage = true;
      this.util.apiErrorHandler(error);
    });
  }

  scrollToBottom() {
    setTimeout(() => {
      if (this.myContent) {
        this.myContent.scrollToBottom(300);
      }
    }, 100);
  }

  back() {
    this.navCtrl.back();
  }
}
 

Closing Thoughts

Domaca Hrana Shop is more than an e-commerce platform—it’s a mission to make healthy, local food accessible while supporting small farmers. Building it required balancing six interconnected apps, AI integrations, and seamless CI/CD—all while staying true to a social mission.

If you’re passionate about ethical tech, food sustainability, or cross-platform development, I’d love to connect!


Contact Developer

Comments

Popular posts from this blog

Building ASP.NET Core app strengthened with AngularJS 2

Thing Translator

Building REST API service that performs image labelling