EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode bayar, tukar EnakCoin, transfer - PIN (buat, ganti, lupa), outlet, riwayat order, game spin - Halaman community, webview, coming soon, dan ikon menu beranda baru - Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR - Hapus customer point loader, menu, dan checkout lama - Base URL ke enaklo-pos-api.altru.id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5954510b93
commit
f5e3075205
@@ -0,0 +1,298 @@
|
||||
import '../presentation/components/assets/assets.gen.dart';
|
||||
|
||||
// Models
|
||||
class CommunityMember {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? badge;
|
||||
|
||||
const CommunityMember({required this.id, required this.name, this.badge});
|
||||
|
||||
String get initials {
|
||||
final parts = name.trim().split(RegExp(r'\s+'));
|
||||
if (parts.length == 1) return parts.first.substring(0, 1).toUpperCase();
|
||||
return (parts[0][0] + parts[1][0]).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
class CommunityGroup {
|
||||
final String id;
|
||||
final String name;
|
||||
final String emoji;
|
||||
final String description;
|
||||
final String coverImage;
|
||||
final int memberCount;
|
||||
final int postPerDay;
|
||||
final List<String> tags;
|
||||
final bool isJoined;
|
||||
|
||||
/// Indeks warna lembut di [CommunityColor.tints].
|
||||
final int tint;
|
||||
|
||||
const CommunityGroup({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.emoji,
|
||||
required this.description,
|
||||
required this.coverImage,
|
||||
required this.memberCount,
|
||||
required this.postPerDay,
|
||||
required this.tags,
|
||||
required this.tint,
|
||||
this.isJoined = false,
|
||||
});
|
||||
}
|
||||
|
||||
class CommunityPost {
|
||||
final String id;
|
||||
final CommunityMember author;
|
||||
final CommunityGroup group;
|
||||
final String content;
|
||||
final String? image;
|
||||
final String? merchantName;
|
||||
final DateTime createdAt;
|
||||
final int likeCount;
|
||||
final int commentCount;
|
||||
final bool isLiked;
|
||||
|
||||
const CommunityPost({
|
||||
required this.id,
|
||||
required this.author,
|
||||
required this.group,
|
||||
required this.content,
|
||||
required this.createdAt,
|
||||
this.image,
|
||||
this.merchantName,
|
||||
this.likeCount = 0,
|
||||
this.commentCount = 0,
|
||||
this.isLiked = false,
|
||||
});
|
||||
}
|
||||
|
||||
class CommunityComment {
|
||||
final String id;
|
||||
final CommunityMember author;
|
||||
final String content;
|
||||
final DateTime createdAt;
|
||||
final int likeCount;
|
||||
|
||||
const CommunityComment({
|
||||
required this.id,
|
||||
required this.author,
|
||||
required this.content,
|
||||
required this.createdAt,
|
||||
this.likeCount = 0,
|
||||
});
|
||||
}
|
||||
|
||||
class CommunityEvent {
|
||||
final String id;
|
||||
final String groupId;
|
||||
final String title;
|
||||
final String location;
|
||||
final String image;
|
||||
final DateTime startAt;
|
||||
final int attendeeCount;
|
||||
final int quota;
|
||||
final String? reward;
|
||||
final List<CommunityMember> attendees;
|
||||
|
||||
const CommunityEvent({
|
||||
required this.id,
|
||||
required this.groupId,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.image,
|
||||
required this.startAt,
|
||||
required this.attendeeCount,
|
||||
required this.quota,
|
||||
required this.attendees,
|
||||
this.reward,
|
||||
});
|
||||
}
|
||||
|
||||
// Sample data
|
||||
const currentMember = CommunityMember(id: 'me', name: 'Kamu');
|
||||
|
||||
const _members = [
|
||||
CommunityMember(id: 'm1', name: 'Nadia Putri', badge: 'Top Foodie'),
|
||||
CommunityMember(id: 'm2', name: 'Raka Pratama'),
|
||||
CommunityMember(id: 'm3', name: 'Salsa Amelia', badge: 'Kopi Lover'),
|
||||
CommunityMember(id: 'm4', name: 'Dimas Haryo'),
|
||||
CommunityMember(id: 'm5', name: 'Intan Lestari'),
|
||||
CommunityMember(id: 'm6', name: 'Bayu Saputra', badge: 'Pedas Master'),
|
||||
];
|
||||
|
||||
final List<CommunityGroup> communityGroups = [
|
||||
CommunityGroup(
|
||||
id: 'g1',
|
||||
name: 'Pecinta Bakso',
|
||||
emoji: '🍜',
|
||||
description:
|
||||
'Tempat berbagi rekomendasi bakso terenak, dari urat sampai mercon. '
|
||||
'Yuk kulineran bareng!',
|
||||
coverImage: Assets.images.bakso343.path,
|
||||
memberCount: 12480,
|
||||
postPerDay: 86,
|
||||
tags: ['Bakso', 'Kuliner Lokal', 'Review'],
|
||||
tint: 0,
|
||||
isJoined: true,
|
||||
),
|
||||
CommunityGroup(
|
||||
id: 'g2',
|
||||
name: 'Ngopi Santai',
|
||||
emoji: '☕',
|
||||
description:
|
||||
'Obrolan santai soal kopi, manual brew, dan coffee shop nyaman '
|
||||
'buat kerja atau nongkrong.',
|
||||
coverImage: Assets.images.tumulu.path,
|
||||
memberCount: 8920,
|
||||
postPerDay: 54,
|
||||
tags: ['Kopi', 'Nongkrong', 'WFC'],
|
||||
tint: 1,
|
||||
isJoined: true,
|
||||
),
|
||||
CommunityGroup(
|
||||
id: 'g3',
|
||||
name: 'Ramen Hunter',
|
||||
emoji: '🍥',
|
||||
description:
|
||||
'Berburu semangkuk ramen terbaik. Bagikan kuah favoritmu dan '
|
||||
'temukan kedai baru.',
|
||||
coverImage: Assets.images.ramenmulu.path,
|
||||
memberCount: 5310,
|
||||
postPerDay: 31,
|
||||
tags: ['Ramen', 'Jepang'],
|
||||
tint: 2,
|
||||
),
|
||||
CommunityGroup(
|
||||
id: 'g4',
|
||||
name: 'Tim Pedas',
|
||||
emoji: '🌶️',
|
||||
description:
|
||||
'Buat kamu yang nggak kenyang kalau belum kepedesan. Tantangan '
|
||||
'level pedas tiap minggu!',
|
||||
coverImage: Assets.images.woku.path,
|
||||
memberCount: 7640,
|
||||
postPerDay: 47,
|
||||
tags: ['Pedas', 'Challenge'],
|
||||
tint: 3,
|
||||
),
|
||||
];
|
||||
|
||||
final List<CommunityPost> communityPosts = [
|
||||
CommunityPost(
|
||||
id: 'p1',
|
||||
author: _members[0],
|
||||
group: communityGroups[0],
|
||||
content:
|
||||
'Akhirnya nyobain bakso urat di Bakso 343 🤤 Kuahnya bening tapi '
|
||||
'kaldunya kerasa banget, uratnya juga kenyal pas. Porsi besar, '
|
||||
'harga masih ramah dompet. Wajib coba sambal ijonya!',
|
||||
image: Assets.images.bakso343.path,
|
||||
merchantName: 'Bakso 343',
|
||||
createdAt: DateTime.now().subtract(const Duration(minutes: 25)),
|
||||
likeCount: 128,
|
||||
commentCount: 24,
|
||||
isLiked: true,
|
||||
),
|
||||
CommunityPost(
|
||||
id: 'p2',
|
||||
author: _members[2],
|
||||
group: communityGroups[1],
|
||||
content:
|
||||
'Rekomendasi tempat WFC yang colokannya banyak dan wifi kencang? '
|
||||
'Aku biasanya di Tumulu Coffee, tapi pengen coba suasana baru ☕',
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
likeCount: 42,
|
||||
commentCount: 31,
|
||||
),
|
||||
CommunityPost(
|
||||
id: 'p3',
|
||||
author: _members[5],
|
||||
group: communityGroups[3],
|
||||
content:
|
||||
'Level 5 Woku Pedas sukses kutaklukkan 🔥 Yang berani ikut challenge '
|
||||
'minggu depan, kumpul di komen ya!',
|
||||
image: Assets.images.woku.path,
|
||||
merchantName: 'Woku Pedas',
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||||
likeCount: 256,
|
||||
commentCount: 58,
|
||||
),
|
||||
CommunityPost(
|
||||
id: 'p4',
|
||||
author: _members[1],
|
||||
group: communityGroups[2],
|
||||
content:
|
||||
'Shoyu ramen di Ramenmulu ternyata enak banget, chashu-nya lumer. '
|
||||
'Next mau coba yang tantan.',
|
||||
image: Assets.images.ramenmulu.path,
|
||||
merchantName: 'Ramenmulu',
|
||||
createdAt: DateTime.now().subtract(const Duration(days: 1, hours: 3)),
|
||||
likeCount: 87,
|
||||
commentCount: 12,
|
||||
),
|
||||
];
|
||||
|
||||
final List<CommunityComment> communityComments = [
|
||||
CommunityComment(
|
||||
id: 'c1',
|
||||
author: _members[3],
|
||||
content: 'Setuju! Sambal ijonya juara, aku sampai nambah dua kali 😆',
|
||||
createdAt: DateTime.now().subtract(const Duration(minutes: 12)),
|
||||
likeCount: 8,
|
||||
),
|
||||
CommunityComment(
|
||||
id: 'c2',
|
||||
author: _members[4],
|
||||
content: 'Buka sampai jam berapa ya kak? Mau mampir pulang kerja.',
|
||||
createdAt: DateTime.now().subtract(const Duration(minutes: 8)),
|
||||
likeCount: 2,
|
||||
),
|
||||
CommunityComment(
|
||||
id: 'c3',
|
||||
author: _members[0],
|
||||
content: '@Intan sampai jam 10 malam kok, aman 👍',
|
||||
createdAt: DateTime.now().subtract(const Duration(minutes: 3)),
|
||||
likeCount: 1,
|
||||
),
|
||||
];
|
||||
|
||||
final List<CommunityEvent> communityEvents = [
|
||||
CommunityEvent(
|
||||
id: 'e1',
|
||||
groupId: 'g1',
|
||||
title: 'Kopdar Pecinta Bakso',
|
||||
location: 'Bakso 343, Jakarta Selatan',
|
||||
image: Assets.images.bakso343.path,
|
||||
startAt: DateTime.now().add(const Duration(days: 4, hours: 2)),
|
||||
attendeeCount: 38,
|
||||
quota: 50,
|
||||
reward: '+500 EnakPoint',
|
||||
attendees: _members.sublist(0, 4),
|
||||
),
|
||||
CommunityEvent(
|
||||
id: 'e2',
|
||||
groupId: 'g2',
|
||||
title: 'Cupping Session Bareng Barista',
|
||||
location: 'Tumulu Coffee, Bandung',
|
||||
image: Assets.images.tumulu.path,
|
||||
startAt: DateTime.now().add(const Duration(days: 9)),
|
||||
attendeeCount: 18,
|
||||
quota: 20,
|
||||
attendees: _members.sublist(2, 5),
|
||||
),
|
||||
CommunityEvent(
|
||||
id: 'e3',
|
||||
groupId: 'g4',
|
||||
title: 'Spicy Challenge Level 10',
|
||||
location: 'Woku Pedas, Surabaya',
|
||||
image: Assets.images.woku.path,
|
||||
startAt: DateTime.now().add(const Duration(days: 15)),
|
||||
attendeeCount: 64,
|
||||
quota: 100,
|
||||
reward: '+1.000 EnakCoin',
|
||||
attendees: _members.sublist(3),
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,139 @@
|
||||
// Models
|
||||
enum RewardStatus { active, used, expired }
|
||||
|
||||
class Reward {
|
||||
final String id;
|
||||
final String name;
|
||||
final String emoji;
|
||||
final String description;
|
||||
final String categoryName;
|
||||
final int pointsUsed;
|
||||
final DateTime redeemedAt;
|
||||
final DateTime validUntil;
|
||||
final RewardStatus status;
|
||||
final String? couponCode;
|
||||
final DateTime? usedAt;
|
||||
final String? termsAndConditions;
|
||||
|
||||
const Reward({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.emoji,
|
||||
required this.description,
|
||||
required this.categoryName,
|
||||
required this.pointsUsed,
|
||||
required this.redeemedAt,
|
||||
required this.validUntil,
|
||||
required this.status,
|
||||
this.couponCode,
|
||||
this.usedAt,
|
||||
this.termsAndConditions,
|
||||
});
|
||||
}
|
||||
|
||||
// Sample Data
|
||||
//
|
||||
// 1 EnakPoint = potongan Rp 1, jadi harga reward dibuat sedikit di bawah harga
|
||||
// menunya supaya menukar lebih untung daripada bayar langsung pakai poin.
|
||||
// Menu gratis berlaku 14 hari dan voucher potongan 30 hari sejak ditukar.
|
||||
DateTime _daysAgo(int days) => DateTime.now().subtract(Duration(days: days));
|
||||
DateTime _daysFromNow(int days) => DateTime.now().add(Duration(days: days));
|
||||
|
||||
/// Getter, bukan `final`, supaya perubahan data langsung terbaca saat hot
|
||||
/// reload (variabel global yang sudah terisi tidak diisi ulang).
|
||||
List<Reward> get rewardSamples => [
|
||||
Reward(
|
||||
id: 'r1',
|
||||
name: 'Kopi Susu',
|
||||
emoji: '☕',
|
||||
description: '1 Kopi Susu regular di Tumulu Coffee',
|
||||
categoryName: 'Minuman',
|
||||
pointsUsed: 20000,
|
||||
redeemedAt: _daysAgo(11),
|
||||
validUntil: _daysFromNow(3),
|
||||
status: RewardStatus.active,
|
||||
couponCode: 'ENK-7QM2PX',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 Kopi Susu ukuran regular di Tumulu Coffee, dine-in '
|
||||
'atau take away. Upsize dan tambahan topping dibayar terpisah. '
|
||||
'Tunjukkan kode ke kasir sebelum membayar.',
|
||||
),
|
||||
Reward(
|
||||
id: 'r2',
|
||||
name: 'Bakso Urat',
|
||||
emoji: '🍲',
|
||||
description: '1 porsi Bakso Urat di Bakso 343',
|
||||
categoryName: 'Makanan',
|
||||
pointsUsed: 18000,
|
||||
redeemedAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||||
validUntil: _daysFromNow(14),
|
||||
status: RewardStatus.active,
|
||||
couponCode: 'ENK-B3HW8N',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 porsi Bakso Urat di Bakso 343, dine-in atau take '
|
||||
'away. Tunjukkan kode ke kasir sebelum membayar.',
|
||||
),
|
||||
Reward(
|
||||
id: 'r3',
|
||||
name: 'Potongan Rp 10.000',
|
||||
emoji: '🎟️',
|
||||
description: 'Min. belanja Rp 50.000 di semua outlet',
|
||||
categoryName: 'Voucher',
|
||||
pointsUsed: 8000,
|
||||
redeemedAt: _daysAgo(2),
|
||||
validUntil: _daysFromNow(28),
|
||||
status: RewardStatus.active,
|
||||
couponCode: 'ENK-K9D4TR',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 transaksi dengan minimum belanja Rp 50.000 di semua '
|
||||
'outlet. Tidak dapat digabung dengan voucher lain dan tidak dapat '
|
||||
'diuangkan.',
|
||||
),
|
||||
Reward(
|
||||
id: 'r4',
|
||||
name: 'Es Teh Manis',
|
||||
emoji: '🥤',
|
||||
description: '1 gelas Es Teh Manis di semua outlet',
|
||||
categoryName: 'Minuman',
|
||||
pointsUsed: 4000,
|
||||
redeemedAt: _daysAgo(9),
|
||||
validUntil: _daysFromNow(5),
|
||||
usedAt: _daysAgo(8),
|
||||
status: RewardStatus.used,
|
||||
couponCode: 'ENK-5TRJ6C',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 gelas Es Teh Manis ukuran regular di semua outlet.',
|
||||
),
|
||||
Reward(
|
||||
id: 'r5',
|
||||
name: 'Gyoza Goreng',
|
||||
emoji: '🥟',
|
||||
description: '5 pcs Gyoza goreng di Ramenmulu',
|
||||
categoryName: 'Cemilan',
|
||||
pointsUsed: 16000,
|
||||
redeemedAt: _daysAgo(20),
|
||||
validUntil: _daysAgo(6),
|
||||
usedAt: _daysAgo(16),
|
||||
status: RewardStatus.used,
|
||||
couponCode: 'ENK-G8VL2F',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 porsi Gyoza goreng isi 5 di Ramenmulu. Tidak berlaku '
|
||||
'untuk menu paket.',
|
||||
),
|
||||
Reward(
|
||||
id: 'r6',
|
||||
name: 'Potongan Rp 5.000',
|
||||
emoji: '🎟️',
|
||||
description: 'Min. belanja Rp 30.000 di semua outlet',
|
||||
categoryName: 'Voucher',
|
||||
pointsUsed: 4000,
|
||||
redeemedAt: _daysAgo(50),
|
||||
validUntil: _daysAgo(20),
|
||||
status: RewardStatus.expired,
|
||||
couponCode: 'ENK-P2XN7D',
|
||||
termsAndConditions:
|
||||
'Berlaku untuk 1 transaksi dengan minimum belanja Rp 30.000 di semua '
|
||||
'outlet. Tidak dapat digabung dengan voucher lain dan tidak dapat '
|
||||
'diuangkan.',
|
||||
),
|
||||
];
|
||||
Reference in New Issue
Block a user