Domaca Hrana Shop
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 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 Web App domacahrana.shop/home Login: info@infinityapps.ai / Password: Test123! |
|
|
User Mobile App Google Play Store |
|
|
Store Web App storeapp.domacahrana.shop Login: jaskobh@hotmail.com / Password: Test123! |
|
|
Store Mobile App Google Play Store
|
|
|
Admin Panel admin.domacahrana.shop Login: domacahranashop@gmail.com / Password: Test123!
|
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!
Comments
Post a Comment