Compare commits
No commits in common. "9a40854884ea0c7337f8f5ccfd679d7785984765" and "fa0e43885ac23b6e4ef9423ec3d4eeca017c7cd3" have entirely different histories.
9a40854884
...
fa0e43885a
|
|
@ -1,38 +0,0 @@
|
||||||
name: Build and Deploy Chitfund
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Build and push Backend
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: ./backend
|
|
||||||
push: true
|
|
||||||
tags: 192.168.8.250:5000/chitfund-backend:latest
|
|
||||||
# Local registry is insecure
|
|
||||||
labels: |
|
|
||||||
org.opencontainers.image.source=${{ gitea.repository_url }}
|
|
||||||
|
|
||||||
build-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Build and push Frontend
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: ./luckychit
|
|
||||||
push: true
|
|
||||||
tags: 192.168.8.250:5000/chitfund-frontend:latest
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Backend Dockerfile for Chitfund
|
|
||||||
FROM node:20-slim
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install build essentials for bcrypt if needed (though pre-built binaries usually work on slim)
|
|
||||||
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install --omit=dev
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
|
|
@ -633,7 +633,7 @@ const deleteMonthlyDraw = async (req, res) => {
|
||||||
const updateMonthlyDraw = async (req, res) => {
|
const updateMonthlyDraw = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { draw_id } = req.params;
|
const { draw_id } = req.params;
|
||||||
const { winner_id, prize_amount, notes, month, year, draw_date } = req.body;
|
const { winner_id, prize_amount, notes } = req.body;
|
||||||
const managerId = req.user.id;
|
const managerId = req.user.id;
|
||||||
|
|
||||||
// Find the draw
|
// Find the draw
|
||||||
|
|
@ -664,120 +664,41 @@ const updateMonthlyDraw = async (req, res) => {
|
||||||
|
|
||||||
const updates = {};
|
const updates = {};
|
||||||
|
|
||||||
let nextMonth = monthlyDraw.month;
|
// Update winner if provided
|
||||||
let nextYear = monthlyDraw.year;
|
|
||||||
const monthProvided = month !== undefined && month !== null && month !== '';
|
|
||||||
const yearProvided = year !== undefined && year !== null && year !== '';
|
|
||||||
if (monthProvided) {
|
|
||||||
nextMonth = parseInt(month, 10);
|
|
||||||
}
|
|
||||||
if (yearProvided) {
|
|
||||||
nextYear = parseInt(year, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (monthProvided || yearProvided) {
|
|
||||||
if (Number.isNaN(nextMonth) || nextMonth < 1 || nextMonth > 12) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Month must be between 1 and 12'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (Number.isNaN(nextYear) || nextYear < 2020) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Year must be 2020 or later'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const conflict = await MonthlyDraw.findOne({
|
|
||||||
where: {
|
|
||||||
group_id: monthlyDraw.group_id,
|
|
||||||
month: nextMonth,
|
|
||||||
year: nextYear,
|
|
||||||
id: { [Op.ne]: draw_id }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (conflict) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: `Another draw already exists for ${nextMonth}/${nextYear}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updates.month = nextMonth;
|
|
||||||
updates.year = nextYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draw_date !== undefined && draw_date !== null && draw_date !== '') {
|
|
||||||
const d = new Date(draw_date);
|
|
||||||
if (Number.isNaN(d.getTime())) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Invalid draw_date'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updates.draw_date = d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update winner if provided and different from current
|
|
||||||
if (winner_id) {
|
if (winner_id) {
|
||||||
const winnerStr = String(winner_id);
|
// Verify the winner is a member of the group
|
||||||
const currentWinnerStr = monthlyDraw.winner_id != null ? String(monthlyDraw.winner_id) : '';
|
const member = await GroupMember.findOne({
|
||||||
if (winnerStr !== currentWinnerStr) {
|
where: {
|
||||||
const member = await GroupMember.findOne({
|
group_id: monthlyDraw.ChitGroup.id,
|
||||||
where: {
|
user_id: winner_id,
|
||||||
group_id: monthlyDraw.ChitGroup.id,
|
status: 'active'
|
||||||
user_id: winner_id,
|
},
|
||||||
status: 'active'
|
include: [{ model: User, attributes: ['id', 'full_name'] }]
|
||||||
},
|
});
|
||||||
include: [{ model: User, attributes: ['id', 'full_name'] }]
|
|
||||||
|
if (!member) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Winner must be an active member of this group'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!member) {
|
updates.winner_id = winner_id;
|
||||||
return res.status(400).json({
|
if (!notes) {
|
||||||
success: false,
|
updates.notes = `Winner updated to: ${member.User.full_name}`;
|
||||||
message: 'Winner must be an active member of this group'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const duplicateWinner = await MonthlyDraw.findOne({
|
|
||||||
where: {
|
|
||||||
group_id: monthlyDraw.group_id,
|
|
||||||
winner_id,
|
|
||||||
id: { [Op.ne]: draw_id }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (duplicateWinner) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: `${member.User.full_name} has already won in another draw. Each member can only win once.`,
|
|
||||||
alreadyWon: true,
|
|
||||||
winnerName: member.User.full_name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.winner_id = winner_id;
|
|
||||||
if (notes === undefined || notes === null) {
|
|
||||||
updates.notes = `Winner updated to: ${member.User.full_name}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update prize amount if provided (including 0)
|
// Update prize amount if provided
|
||||||
if (prize_amount != null && prize_amount !== '') {
|
if (prize_amount) {
|
||||||
updates.prize_amount = prize_amount;
|
updates.prize_amount = prize_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update notes if provided
|
// Update notes if provided
|
||||||
if (notes !== undefined && notes !== null) {
|
if (notes) {
|
||||||
updates.notes = notes;
|
updates.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(updates).length === 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'No valid fields to update'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform update
|
// Perform update
|
||||||
await monthlyDraw.update(updates);
|
await monthlyDraw.update(updates);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Frontend Dockerfile for Chitfund (Flutter Web)
|
|
||||||
FROM node:20-slim
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# We serve the build/web directory.
|
|
||||||
# NOTE: This Dockerfile assumes you have already run 'flutter build web'
|
|
||||||
# or we will use a multi-stage build if you prefer.
|
|
||||||
# For simplicity in this home lab, we copy the build output.
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install express
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
CMD ["node", "web-server.js"]
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
output-class: AppLocalizations
|
||||||
|
synthetic-package: false
|
||||||
|
output-dir: lib/l10n
|
||||||
|
|
@ -581,18 +581,13 @@ class ApiService {
|
||||||
if (e.response != null) {
|
if (e.response != null) {
|
||||||
final responseData = e.response?.data;
|
final responseData = e.response?.data;
|
||||||
String message = 'Request failed';
|
String message = 'Request failed';
|
||||||
|
|
||||||
if (responseData is Map<String, dynamic>) {
|
if (responseData is Map<String, dynamic>) {
|
||||||
message = responseData['message']?.toString() ?? 'Request failed';
|
message = responseData['message']?.toString() ?? 'Request failed';
|
||||||
final merged = Map<String, dynamic>.from(responseData);
|
|
||||||
merged['success'] = false;
|
|
||||||
merged['message'] = message;
|
|
||||||
merged['statusCode'] = e.response?.statusCode;
|
|
||||||
return merged;
|
|
||||||
} else if (responseData is String) {
|
} else if (responseData is String) {
|
||||||
message = responseData;
|
message = responseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': message,
|
'message': message,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import '../../core/services/auth_service.dart';
|
||||||
import '../../core/services/api_service.dart';
|
import '../../core/services/api_service.dart';
|
||||||
import '../../core/utils/snackbar_util.dart';
|
import '../../core/utils/snackbar_util.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../../l10n/l10n_x.dart';
|
|
||||||
|
|
||||||
String _themeModeLabel(AppLocalizations l10n) {
|
String _themeModeLabel(AppLocalizations l10n) {
|
||||||
switch (ThemeController.to.themeMode) {
|
switch (ThemeController.to.themeMode) {
|
||||||
|
|
@ -26,7 +25,7 @@ class SettingsPage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = AppLocalizations.of(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(l10n.settingsTitle),
|
title: Text(l10n.settingsTitle),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import '../../core/services/chit_group_service.dart';
|
import '../../core/services/chit_group_service.dart';
|
||||||
import '../../core/services/api_service.dart';
|
import '../../core/services/api_service.dart';
|
||||||
import '../../core/models/monthly_draw.dart';
|
import '../../core/models/monthly_draw.dart';
|
||||||
|
|
@ -27,33 +26,6 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
|
|
||||||
String? _selectedMemberId;
|
String? _selectedMemberId;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
late int _editMonth;
|
|
||||||
late int _editYear;
|
|
||||||
late DateTime _drawDate;
|
|
||||||
|
|
||||||
static const _monthNames = <String>[
|
|
||||||
'', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
|
||||||
];
|
|
||||||
|
|
||||||
DateTime _baselineDrawDate() {
|
|
||||||
final d = widget.draw.drawDate;
|
|
||||||
if (d.millisecondsSinceEpoch == 0) {
|
|
||||||
return DateTime(widget.draw.year, widget.draw.month, 1);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _sameCalendarDay(DateTime a, DateTime b) {
|
|
||||||
return a.year == b.year && a.month == b.month && a.day == b.day;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _clampEditYear(int y) {
|
|
||||||
final minY = 2020;
|
|
||||||
final maxY = DateTime.now().year + 1;
|
|
||||||
if (y < minY) return minY;
|
|
||||||
if (y > maxY) return maxY;
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -61,9 +33,6 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
_selectedMemberId = widget.draw.winnerId;
|
_selectedMemberId = widget.draw.winnerId;
|
||||||
_prizeAmountController.text = widget.draw.prizeAmount.toStringAsFixed(0);
|
_prizeAmountController.text = widget.draw.prizeAmount.toStringAsFixed(0);
|
||||||
_notesController.text = widget.draw.notes ?? '';
|
_notesController.text = widget.draw.notes ?? '';
|
||||||
_editMonth = widget.draw.month.clamp(1, 12);
|
|
||||||
_editYear = _clampEditYear(widget.draw.year);
|
|
||||||
_drawDate = _baselineDrawDate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -73,18 +42,6 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pickDrawDate() async {
|
|
||||||
final picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: _drawDate,
|
|
||||||
firstDate: DateTime(2020),
|
|
||||||
lastDate: DateTime.now().add(const Duration(days: 365 * 3)),
|
|
||||||
);
|
|
||||||
if (picked != null) {
|
|
||||||
setState(() => _drawDate = picked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleSubmit() async {
|
Future<void> _handleSubmit() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
if (_selectedMemberId == null) {
|
if (_selectedMemberId == null) {
|
||||||
|
|
@ -97,16 +54,7 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
try {
|
try {
|
||||||
final updates = <String, dynamic>{};
|
final updates = <String, dynamic>{};
|
||||||
|
|
||||||
if (_editMonth != widget.draw.month) {
|
// Only include changed fields
|
||||||
updates['month'] = _editMonth;
|
|
||||||
}
|
|
||||||
if (_editYear != widget.draw.year) {
|
|
||||||
updates['year'] = _editYear;
|
|
||||||
}
|
|
||||||
if (!_sameCalendarDay(_drawDate, _baselineDrawDate())) {
|
|
||||||
updates['draw_date'] = _drawDate.toUtc().toIso8601String();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_selectedMemberId != widget.draw.winnerId) {
|
if (_selectedMemberId != widget.draw.winnerId) {
|
||||||
updates['winner_id'] = _selectedMemberId;
|
updates['winner_id'] = _selectedMemberId;
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +64,7 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
updates['prize_amount'] = newPrizeAmount;
|
updates['prize_amount'] = newPrizeAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_notesController.text != (widget.draw.notes ?? '')) {
|
if (_notesController.text.isNotEmpty && _notesController.text != widget.draw.notes) {
|
||||||
updates['notes'] = _notesController.text;
|
updates['notes'] = _notesController.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,20 +83,10 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
);
|
);
|
||||||
Get.back(result: true);
|
Get.back(result: true);
|
||||||
} else {
|
} else {
|
||||||
final isAlreadyWon = response['alreadyWon'] ?? false;
|
SnackbarUtil.showError(
|
||||||
final winnerName = response['winnerName'] ?? '';
|
response['message'] ?? 'Failed to update draw',
|
||||||
if (isAlreadyWon && winnerName.isNotEmpty) {
|
title: 'Error',
|
||||||
SnackbarUtil.showError(
|
);
|
||||||
'$winnerName has already won in another draw.\nEach member can only win once.',
|
|
||||||
title: 'Duplicate winner',
|
|
||||||
duration: const Duration(seconds: 4),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
SnackbarUtil.showError(
|
|
||||||
response['message'] ?? 'Failed to update draw',
|
|
||||||
title: 'Error',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SnackbarUtil.showError('Error: ${e.toString()}');
|
SnackbarUtil.showError('Error: ${e.toString()}');
|
||||||
|
|
@ -199,7 +137,7 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Originally ${widget.draw.month}/${widget.draw.year}',
|
'Month ${widget.draw.month}/${widget.draw.year}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
|
|
@ -253,108 +191,6 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
),
|
),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
Text(
|
|
||||||
'Draw month & year',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: DropdownButtonFormField<int>(
|
|
||||||
value: _editMonth,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Month',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey.shade50,
|
|
||||||
),
|
|
||||||
items: List.generate(12, (i) {
|
|
||||||
final m = i + 1;
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: m,
|
|
||||||
child: Text('${_monthNames[m]} ($m)'),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v != null) setState(() => _editMonth = v);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12.w),
|
|
||||||
Expanded(
|
|
||||||
child: DropdownButtonFormField<int>(
|
|
||||||
value: _editYear,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Year',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey.shade50,
|
|
||||||
),
|
|
||||||
items: List.generate(
|
|
||||||
DateTime.now().year - 2019 + 2,
|
|
||||||
(i) {
|
|
||||||
final y = 2020 + i;
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: y,
|
|
||||||
child: Text('$y'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v != null) setState(() => _editYear = v);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Text(
|
|
||||||
'This is the calendar month this draw belongs to. It must not clash with another draw in this group.',
|
|
||||||
style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
'Draw date',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
InkWell(
|
|
||||||
onTap: _pickDrawDate,
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
child: InputDecorator(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.calendar_today_outlined),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey.shade50,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
DateFormat('d MMM yyyy').format(_drawDate),
|
|
||||||
style: TextStyle(fontSize: 16.sp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Text(
|
|
||||||
'When the draw actually happened (shown in history).',
|
|
||||||
style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
|
|
||||||
// Select Winner
|
// Select Winner
|
||||||
Text(
|
Text(
|
||||||
'Winner',
|
'Winner',
|
||||||
|
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
{
|
|
||||||
"@memberWelcomeGreeting": {
|
|
||||||
"placeholders": {
|
|
||||||
"name": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"memberWelcomeGreeting": "Welcome, {name}!",
|
|
||||||
"ok": "OK",
|
|
||||||
"save": "Save",
|
|
||||||
"retry": "Retry",
|
|
||||||
"delete": "Delete",
|
|
||||||
"userLabel": "User",
|
|
||||||
"managerFallbackName": "Manager",
|
|
||||||
"snackTitleError": "Error",
|
|
||||||
"snackTitleSuccess": "Success",
|
|
||||||
"operationFailedShort": "Failed",
|
|
||||||
"failedLoadChitGroups": "Failed to load chit groups",
|
|
||||||
"chitfundCreatedSuccess": "Chitfund created successfully",
|
|
||||||
"failedCreateChitGroup": "Failed to create chit group",
|
|
||||||
"failedUpdateChitGroup": "Failed to update chit group",
|
|
||||||
"chitGroupDeletedSuccess": "Chit group deleted successfully",
|
|
||||||
"failedDeleteChitGroup": "Failed to delete chit group",
|
|
||||||
"failedLoadGroupDetails": "Failed to load group details",
|
|
||||||
"failedLoadGroupMembers": "Failed to load group members",
|
|
||||||
"memberAddedSuccess": "Member added successfully",
|
|
||||||
"failedAddMember": "Failed to add member",
|
|
||||||
"memberRemovedSuccess": "Member removed successfully",
|
|
||||||
"failedRemoveMember": "Failed to remove member",
|
|
||||||
"memberStatusUpdatedSuccess": "Member status updated successfully",
|
|
||||||
"failedUpdateMemberStatus": "Failed to update member status",
|
|
||||||
"failedLoadPayments": "Failed to load payments",
|
|
||||||
"paymentRecordedSuccess": "Payment recorded successfully",
|
|
||||||
"failedRecordPayment": "Failed to record payment",
|
|
||||||
"failedLoadGroupStatistics": "Failed to load group statistics",
|
|
||||||
"chitfundStartedSuccess": "Chitfund started successfully",
|
|
||||||
"failedStartChitGroup": "Failed to start chit group",
|
|
||||||
"failedLoadMonthlyDraws": "Failed to load monthly draws",
|
|
||||||
"failedCreateMonthlyDraw": "Failed to create monthly draw",
|
|
||||||
"failedLoadDrawStatistics": "Failed to load draw statistics",
|
|
||||||
"failedLoadFinancialData": "Failed to load financial data",
|
|
||||||
"signupFailedTitle": "Signup Failed",
|
|
||||||
"signupFailedGeneric": "Signup failed. Please try again.",
|
|
||||||
"loginFailedTitle": "Login Failed",
|
|
||||||
"loginFailedGeneric": "Login failed. Please try again.",
|
|
||||||
"passwordChangedSuccess": "Password changed successfully",
|
|
||||||
"failedChangePassword": "Failed to change password",
|
|
||||||
"stateSomethingWentWrong": "Something went wrong",
|
|
||||||
"emptyNoGroupsTitle": "No Chit Groups Yet",
|
|
||||||
"emptyNoGroupsMessage": "You haven't created any chit groups yet.\nCreate your first group or import an existing one!",
|
|
||||||
"emptyNoGroupsAction": "Create Group",
|
|
||||||
"emptyNoMembersTitle": "No Members Yet",
|
|
||||||
"emptyNoMembersMessage": "This group doesn't have any members yet.\nAdd members to get started!",
|
|
||||||
"emptyNoMembersAction": "Add Members",
|
|
||||||
"emptyNoPaymentsTitle": "No Payments Yet",
|
|
||||||
"emptyNoPaymentsMessage": "No payment records found.\nPayments will appear here once recorded.",
|
|
||||||
"emptyNoPaymentsAction": "Record Payment",
|
|
||||||
"emptyNoActivitiesTitle": "No Recent Activities",
|
|
||||||
"emptyNoActivitiesMessage": "Your recent activities will appear here.\nStart using the app to see updates!",
|
|
||||||
"emptyNoActivitiesAction": "Refresh",
|
|
||||||
"emptyNoResultsTitle": "No Results Found",
|
|
||||||
"emptyNoResultsMessage": "We couldn't find what you're looking for.\nTry adjusting your search or filters.",
|
|
||||||
"emptyNoResultsAction": "Clear Filters",
|
|
||||||
"emptyErrorTitle": "Oops! Something Went Wrong",
|
|
||||||
"emptyErrorMessage": "We encountered an error while loading data.\nPlease try again.",
|
|
||||||
"emptyErrorAction": "Retry",
|
|
||||||
"emptyNoInternetTitle": "No Internet Connection",
|
|
||||||
"emptyNoInternetMessage": "Please check your internet connection\nand try again.",
|
|
||||||
"emptyNoInternetAction": "Retry",
|
|
||||||
"dashboardTitle": "Dashboard",
|
|
||||||
"notificationsTooltip": "Notifications",
|
|
||||||
"recordingsTooltip": "View draw recordings",
|
|
||||||
"testDrawTooltip": "Test animated draw",
|
|
||||||
"chitFundManagerRole": "Chit Fund Manager",
|
|
||||||
"menuDashboard": "Dashboard",
|
|
||||||
"menuMyChitfunds": "My Chitfunds",
|
|
||||||
"menuMembers": "Members",
|
|
||||||
"menuPayments": "Payments",
|
|
||||||
"menuLotteryDraws": "Lottery Draws",
|
|
||||||
"menuReports": "Reports",
|
|
||||||
"welcomeBackTitle": "Welcome back!",
|
|
||||||
"welcomeBackSubtitle": "Here's what's happening with your chit funds today.",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"qaCreateChitfundTitle": "Create New Chitfund",
|
|
||||||
"qaCreateChitfundSubtitle": "Start a new chit fund group",
|
|
||||||
"qaImportChitfundTitle": "Import Existing Chitfund",
|
|
||||||
"qaImportChitfundSubtitle": "Add a group that already started",
|
|
||||||
"qaViewAllChitfundsTitle": "View All Chitfunds",
|
|
||||||
"qaViewAllChitfundsSubtitle": "Manage your existing groups",
|
|
||||||
"qaManageMembersTitle": "Manage Members",
|
|
||||||
"qaManageMembersSubtitle": "Add or remove members",
|
|
||||||
"qaPaymentRecordsTitle": "Payment Records",
|
|
||||||
"qaPaymentRecordsSubtitle": "Track all transactions",
|
|
||||||
"sectionMyChitfunds": "My Chitfunds",
|
|
||||||
"viewAll": "View All",
|
|
||||||
"noChitFundsYetShort": "No chit funds yet",
|
|
||||||
"groupStatusActive": "Active",
|
|
||||||
"groupStatusForming": "Forming",
|
|
||||||
"groupStatusCompleted": "Completed",
|
|
||||||
"unnamedGroup": "Unnamed",
|
|
||||||
"actionRecord": "Record",
|
|
||||||
"actionDraw": "Draw",
|
|
||||||
"actionView": "View",
|
|
||||||
"actionManageGroup": "Manage Group",
|
|
||||||
"groupImportedMessage": "Group imported! Now add members and backfill past data.",
|
|
||||||
"groupImportedTitle": "Success",
|
|
||||||
"paymentsPageComingSoon": "Payments page will be implemented next",
|
|
||||||
"comingSoonTitle": "Coming Soon",
|
|
||||||
"pageMyChitfunds": "My Chitfunds",
|
|
||||||
"createNewGroupMenu": "Create New Group",
|
|
||||||
"importExistingGroupMenu": "Import Existing Group",
|
|
||||||
"appDisplayName": "LuckyChit",
|
|
||||||
"authLoginTagline": "Chit fund management that feels effortless.",
|
|
||||||
"authSignupScreenTitle": "Create account",
|
|
||||||
"authSignupTagline": "Set up your profile in under a minute.",
|
|
||||||
"labelMobileNumber": "Mobile number",
|
|
||||||
"labelMobileNumberRequired": "Mobile number *",
|
|
||||||
"labelPassword": "Password",
|
|
||||||
"labelPasswordRequired": "Password *",
|
|
||||||
"labelFullNameRequired": "Full name *",
|
|
||||||
"labelEmailOptional": "Email (optional)",
|
|
||||||
"labelAddressOptional": "Address (optional)",
|
|
||||||
"labelEmergencyContactOptional": "Emergency contact (optional)",
|
|
||||||
"labelConfirmPasswordRequired": "Confirm password *",
|
|
||||||
"validatorEnterMobile": "Please enter mobile number",
|
|
||||||
"validatorMobileTenDigits": "Mobile number must be 10 digits",
|
|
||||||
"validatorMobileDigitsOnly": "Mobile number must contain only digits",
|
|
||||||
"validatorEnterFullName": "Please enter your full name",
|
|
||||||
"validatorValidEmail": "Please enter a valid email address",
|
|
||||||
"validatorEmergencyTenDigits": "Emergency contact must be 10 digits",
|
|
||||||
"validatorEmergencyDigitsOnly": "Emergency contact must contain only digits",
|
|
||||||
"validatorEnterPasswordAuth": "Please enter password",
|
|
||||||
"validatorPasswordMinSixAuth": "Password must be at least 6 characters",
|
|
||||||
"validatorConfirmPassword": "Please confirm password",
|
|
||||||
"validatorPasswordsMismatch": "Passwords do not match",
|
|
||||||
"tooltipShowPassword": "Show password",
|
|
||||||
"tooltipHidePassword": "Hide password",
|
|
||||||
"signInButton": "Sign in",
|
|
||||||
"createAccountButton": "Create account",
|
|
||||||
"alreadyHaveAccount": "Already have an account? ",
|
|
||||||
"loginLink": "Login",
|
|
||||||
"loginInvalidCredentials": "Invalid mobile number or password. Please try again.",
|
|
||||||
"signupSuccessWelcome": "Account created successfully! Welcome to LuckyChit.",
|
|
||||||
"signupFailedGenericUi": "Failed to create account. Please try again.",
|
|
||||||
"featureComingSoonMessage": "Feature coming soon",
|
|
||||||
"memberFallbackName": "Member",
|
|
||||||
"memberSubtitleEmpty": "Join a chit fund to start managing your investments.",
|
|
||||||
"memberSubtitleHasGroups": "Manage your chit fund investments and track your payments.",
|
|
||||||
"navHome": "Home",
|
|
||||||
"navPayments": "Payments",
|
|
||||||
"navNotifications": "Notifications",
|
|
||||||
"navProfile": "Profile",
|
|
||||||
"memberEmptyChitTitle": "No Chit Funds Yet",
|
|
||||||
"memberEmptyChitBody": "You haven't joined any chit funds yet.\nContact your manager to get started!",
|
|
||||||
"memberHowToStartTitle": "How to get started?",
|
|
||||||
"memberHowToStartBody": "1. Your manager will add you to a chit group\n2. You'll receive a notification\n3. Start managing your payments here!",
|
|
||||||
"unnamedGroupLong": "Unnamed Group",
|
|
||||||
"labelTotalValue": "Total Value",
|
|
||||||
"labelDuration": "Duration",
|
|
||||||
"monthsSuffix": "months",
|
|
||||||
"labelInstallment": "Installment",
|
|
||||||
"labelStatus": "Status",
|
|
||||||
"groupStatusPending": "Pending",
|
|
||||||
"payNowButton": "Pay Now",
|
|
||||||
"detailsButton": "Details",
|
|
||||||
"memberInfoNotFound": "Member information not found"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,166 +0,0 @@
|
||||||
{
|
|
||||||
"@memberWelcomeGreeting": {
|
|
||||||
"placeholders": {
|
|
||||||
"name": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"memberWelcomeGreeting": "స్వాగతం, {name}!",
|
|
||||||
"ok": "సరే",
|
|
||||||
"save": "సేవ్",
|
|
||||||
"retry": "మళ్లీ ప్రయత్నించండి",
|
|
||||||
"delete": "తొలగించు",
|
|
||||||
"userLabel": "వినియోగదారు",
|
|
||||||
"managerFallbackName": "మేనేజర్",
|
|
||||||
"snackTitleError": "లోపం",
|
|
||||||
"snackTitleSuccess": "విజయం",
|
|
||||||
"operationFailedShort": "విఫలమైంది",
|
|
||||||
"failedLoadChitGroups": "చిట్ గ్రూప్లను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"chitfundCreatedSuccess": "చిట్ఫండ్ విజయవంతంగా సృష్టించబడింది",
|
|
||||||
"failedCreateChitGroup": "చిట్ గ్రూప్ సృష్టించడంలో విఫలమైంది",
|
|
||||||
"failedUpdateChitGroup": "చిట్ గ్రూప్ నవీకరించడంలో విఫలమైంది",
|
|
||||||
"chitGroupDeletedSuccess": "చిట్ గ్రూప్ విజయవంతంగా తొలగించబడింది",
|
|
||||||
"failedDeleteChitGroup": "చిట్ గ్రూప్ తొలగించడంలో విఫలమైంది",
|
|
||||||
"failedLoadGroupDetails": "గ్రూప్ వివరాలను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"failedLoadGroupMembers": "గ్రూప్ సభ్యులను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"memberAddedSuccess": "సభ్యుడు విజయవంతంగా జోడించబడ్డారు",
|
|
||||||
"failedAddMember": "సభ్యుడిని జోడించడంలో విఫలమైంది",
|
|
||||||
"memberRemovedSuccess": "సభ్యుడు విజయవంతంగా తొలగించబడ్డారు",
|
|
||||||
"failedRemoveMember": "సభ్యుడిని తొలగించడంలో విఫలమైంది",
|
|
||||||
"memberStatusUpdatedSuccess": "సభ్య స్థితి విజయవంతంగా నవీకరించబడింది",
|
|
||||||
"failedUpdateMemberStatus": "సభ్య స్థితిని నవీకరించడంలో విఫలమైంది",
|
|
||||||
"failedLoadPayments": "చెల్లింపులను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"paymentRecordedSuccess": "చెల్లింపు విజయవంతంగా నమోదు చేయబడింది",
|
|
||||||
"failedRecordPayment": "చెల్లింపు నమోదు చేయడంలో విఫలమైంది",
|
|
||||||
"failedLoadGroupStatistics": "గ్రూప్ గణాంకాలను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"chitfundStartedSuccess": "చిట్ఫండ్ విజయవంతంగా ప్రారంభమైంది",
|
|
||||||
"failedStartChitGroup": "చిట్ గ్రూప్ ప్రారంభించడంలో విఫలమైంది",
|
|
||||||
"failedLoadMonthlyDraws": "నెలవారీ డ్రాలను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"failedCreateMonthlyDraw": "నెలవారీ డ్రా సృష్టించడంలో విఫలమైంది",
|
|
||||||
"failedLoadDrawStatistics": "డ్రా గణాంకాలను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"failedLoadFinancialData": "ఆర్థిక డేటాను లోడ్ చేయడంలో విఫలమైంది",
|
|
||||||
"signupFailedTitle": "నమోదు విఫలమైంది",
|
|
||||||
"signupFailedGeneric": "నమోదు విఫలమైంది. దయచేసి మళ్లీ ప్రయత్నించండి.",
|
|
||||||
"loginFailedTitle": "లాగిన్ విఫలమైంది",
|
|
||||||
"loginFailedGeneric": "లాగిన్ విఫలమైంది. దయచేసి మళ్లీ ప్రయత్నించండి.",
|
|
||||||
"passwordChangedSuccess": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది",
|
|
||||||
"failedChangePassword": "పాస్వర్డ్ మార్చడంలో విఫలమైంది",
|
|
||||||
"stateSomethingWentWrong": "ఏదో తప్పు జరిగింది",
|
|
||||||
"emptyNoGroupsTitle": "ఇంకా చిట్ గ్రూప్లు లేవు",
|
|
||||||
"emptyNoGroupsMessage": "మీరు ఇంకా చిట్ గ్రూప్లు సృష్టించలేదు.\nమొదటి గ్రూప్ సృష్టించండి లేదా ఉన్నదాన్ని దిగుమతి చేయండి!",
|
|
||||||
"emptyNoGroupsAction": "గ్రూప్ సృష్టించు",
|
|
||||||
"emptyNoMembersTitle": "ఇంకా సభ్యులు లేరు",
|
|
||||||
"emptyNoMembersMessage": "ఈ గ్రూప్కు ఇంకా సభ్యులు లేరు.\nప్రారంభించడానికి సభ్యులను జోడించండి!",
|
|
||||||
"emptyNoMembersAction": "సభ్యులను జోడించు",
|
|
||||||
"emptyNoPaymentsTitle": "ఇంకా చెల్లింపులు లేవు",
|
|
||||||
"emptyNoPaymentsMessage": "చెల్లింపు రికార్డ్లు కనుగొనబడలేదు.\nనమోదు చేసిన తర్వాత ఇక్కడ కనిపిస్తాయి.",
|
|
||||||
"emptyNoPaymentsAction": "చెల్లింపు నమోదు",
|
|
||||||
"emptyNoActivitiesTitle": "ఇటీవలి కార్యకలాపాలు లేవు",
|
|
||||||
"emptyNoActivitiesMessage": "మీ ఇటీవలి కార్యకలాపాలు ఇక్కడ కనిపిస్తాయి.\nనవీకరణల కోసం యాప్ను ఉపయోగించండి!",
|
|
||||||
"emptyNoActivitiesAction": "రిఫ్రెష్",
|
|
||||||
"emptyNoResultsTitle": "ఫలితాలు లేవు",
|
|
||||||
"emptyNoResultsMessage": "మీరు వెతుకుతున్నది కనుగొనలేకపోయాము.\nశోధన లేదా ఫిల్టర్లను మార్చి ప్రయత్నించండి.",
|
|
||||||
"emptyNoResultsAction": "ఫిల్టర్లు క్లియర్",
|
|
||||||
"emptyErrorTitle": "అయ్యో! ఏదో తప్పు జరిగింది",
|
|
||||||
"emptyErrorMessage": "డేటా లోడ్ చేస్తూ లోపం వచ్చింది.\nదయచేసి మళ్లీ ప్రయత్నించండి.",
|
|
||||||
"emptyErrorAction": "మళ్లీ ప్రయత్నించు",
|
|
||||||
"emptyNoInternetTitle": "ఇంటర్నెట్ కనెక్షన్ లేదు",
|
|
||||||
"emptyNoInternetMessage": "దయచేసి మీ ఇంటర్నెట్ కనెక్షన్ను తనిఖీ చేసి\nమళ్లీ ప్రయత్నించండి.",
|
|
||||||
"emptyNoInternetAction": "మళ్లీ ప్రయత్నించు",
|
|
||||||
"dashboardTitle": "డాష్బోర్డ్",
|
|
||||||
"notificationsTooltip": "నోటిఫికేషన్లు",
|
|
||||||
"recordingsTooltip": "డ్రా రికార్డింగ్లు చూడండి",
|
|
||||||
"testDrawTooltip": "యానిమేటెడ్ డ్రా పరీక్ష",
|
|
||||||
"chitFundManagerRole": "చిట్ ఫండ్ మేనేజర్",
|
|
||||||
"menuDashboard": "డాష్బోర్డ్",
|
|
||||||
"menuMyChitfunds": "నా చిట్ఫండ్లు",
|
|
||||||
"menuMembers": "సభ్యులు",
|
|
||||||
"menuPayments": "చెల్లింపులు",
|
|
||||||
"menuLotteryDraws": "లాటరీ డ్రాలు",
|
|
||||||
"menuReports": "నివేదికలు",
|
|
||||||
"welcomeBackTitle": "మళ్లీ స్వాగతం!",
|
|
||||||
"welcomeBackSubtitle": "ఈ రోజు మీ చిట్ ఫండ్లతో ఏమి జరుగుతోందో ఇక్కడ ఉంది.",
|
|
||||||
"quickActionsTitle": "త్వరిత చర్యలు",
|
|
||||||
"qaCreateChitfundTitle": "కొత్త చిట్ఫండ్ సృష్టించు",
|
|
||||||
"qaCreateChitfundSubtitle": "కొత్త చిట్ ఫండ్ గ్రూప్ ప్రారంభించండి",
|
|
||||||
"qaImportChitfundTitle": "ఉన్న చిట్ఫండ్ దిగుమతి",
|
|
||||||
"qaImportChitfundSubtitle": "ఇప్పటికే ప్రారంభమైన గ్రూప్ జోడించండి",
|
|
||||||
"qaViewAllChitfundsTitle": "అన్ని చిట్ఫండ్లు చూడండి",
|
|
||||||
"qaViewAllChitfundsSubtitle": "మీ గ్రూప్లను నిర్వహించండి",
|
|
||||||
"qaManageMembersTitle": "సభ్యులను నిర్వహించు",
|
|
||||||
"qaManageMembersSubtitle": "సభ్యులను జోడించండి లేదా తొలగించండి",
|
|
||||||
"qaPaymentRecordsTitle": "చెల్లింపు రికార్డ్లు",
|
|
||||||
"qaPaymentRecordsSubtitle": "అన్ని లావాదేవీలను ట్రాక్ చేయండి",
|
|
||||||
"sectionMyChitfunds": "నా చిట్ఫండ్లు",
|
|
||||||
"viewAll": "అన్నీ చూడండి",
|
|
||||||
"noChitFundsYetShort": "ఇంకా చిట్ ఫండ్లు లేవు",
|
|
||||||
"groupStatusActive": "సక్రియం",
|
|
||||||
"groupStatusForming": "ఏర్పాటులో",
|
|
||||||
"groupStatusCompleted": "పూర్తయింది",
|
|
||||||
"unnamedGroup": "పేరులేని",
|
|
||||||
"actionRecord": "నమోదు",
|
|
||||||
"actionDraw": "డ్రా",
|
|
||||||
"actionView": "చూడండి",
|
|
||||||
"actionManageGroup": "గ్రూప్ నిర్వహణ",
|
|
||||||
"groupImportedMessage": "గ్రూప్ దిగుమతి అయ్యింది! ఇప్పుడు సభ్యులను జోడించి గత డేటా నింపండి.",
|
|
||||||
"groupImportedTitle": "విజయం",
|
|
||||||
"paymentsPageComingSoon": "చెల్లింపుల పేజీ తదుపరి అమలు చేయబడుతుంది",
|
|
||||||
"comingSoonTitle": "త్వరలో",
|
|
||||||
"pageMyChitfunds": "నా చిట్ఫండ్లు",
|
|
||||||
"createNewGroupMenu": "కొత్త గ్రూప్ సృష్టించు",
|
|
||||||
"importExistingGroupMenu": "ఉన్న గ్రూప్ దిగుమతి",
|
|
||||||
"appDisplayName": "LuckyChit",
|
|
||||||
"authLoginTagline": "చిట్ ఫండ్ నిర్వహణ సులభంగా అనిపించేలా.",
|
|
||||||
"authSignupScreenTitle": "ఖాతా సృష్టించు",
|
|
||||||
"authSignupTagline": "ఒక నిమిషంలో మీ ప్రొఫైల్ సెటప్ చేయండి.",
|
|
||||||
"labelMobileNumber": "మొబైల్ నంబర్",
|
|
||||||
"labelMobileNumberRequired": "మొబైల్ నంబర్ *",
|
|
||||||
"labelPassword": "పాస్వర్డ్",
|
|
||||||
"labelPasswordRequired": "పాస్వర్డ్ *",
|
|
||||||
"labelFullNameRequired": "పూర్తి పేరు *",
|
|
||||||
"labelEmailOptional": "ఇమెయిల్ (ఐచ్ఛికం)",
|
|
||||||
"labelAddressOptional": "చిరునామా (ఐచ్ఛికం)",
|
|
||||||
"labelEmergencyContactOptional": "అత్యవసర సంప్రదింపు (ఐచ్ఛికం)",
|
|
||||||
"labelConfirmPasswordRequired": "పాస్వర్డ్ నిర్ధారించు *",
|
|
||||||
"validatorEnterMobile": "దయచేసి మొబైల్ నంబర్ నమోదు చేయండి",
|
|
||||||
"validatorMobileTenDigits": "మొబైల్ నంబర్ 10 అంకెలు ఉండాలి",
|
|
||||||
"validatorMobileDigitsOnly": "మొబైల్ నంబర్లో అంకెలు మాత్రమే ఉండాలి",
|
|
||||||
"validatorEnterFullName": "దయచేసి మీ పూర్తి పేరు నమోదు చేయండి",
|
|
||||||
"validatorValidEmail": "దయచేసి సరైన ఇమెయిల్ చిరునామా నమోదు చేయండి",
|
|
||||||
"validatorEmergencyTenDigits": "అత్యవసర సంప్రదింపు 10 అంకెలు ఉండాలి",
|
|
||||||
"validatorEmergencyDigitsOnly": "అత్యవసర సంప్రదింపులో అంకెలు మాత్రమే ఉండాలి",
|
|
||||||
"validatorEnterPasswordAuth": "దయచేసి పాస్వర్డ్ నమోదు చేయండి",
|
|
||||||
"validatorPasswordMinSixAuth": "పాస్వర్డ్ కనీసం 6 అక్షరాలు ఉండాలి",
|
|
||||||
"validatorConfirmPassword": "దయచేసి పాస్వర్డ్ నిర్ధారించండి",
|
|
||||||
"validatorPasswordsMismatch": "పాస్వర్డ్లు సరిపోలడం లేదు",
|
|
||||||
"tooltipShowPassword": "పాస్వర్డ్ చూపు",
|
|
||||||
"tooltipHidePassword": "పాస్వర్డ్ దాచు",
|
|
||||||
"signInButton": "సైన్ ఇన్",
|
|
||||||
"createAccountButton": "ఖాతా సృష్టించు",
|
|
||||||
"alreadyHaveAccount": "ఇప్పటికే ఖాతా ఉందా? ",
|
|
||||||
"loginLink": "లాగిన్",
|
|
||||||
"loginInvalidCredentials": "తప్పు మొబైల్ నంబర్ లేదా పాస్వర్డ్. మళ్లీ ప్రయత్నించండి.",
|
|
||||||
"signupSuccessWelcome": "ఖాతా విజయవంతంగా సృష్టించబడింది! LuckyChitకు స్వాగతం.",
|
|
||||||
"signupFailedGenericUi": "ఖాతా సృష్టించడంలో విఫలమైంది. మళ్లీ ప్రయత్నించండి.",
|
|
||||||
"featureComingSoonMessage": "ఫీచర్ త్వరలో వస్తుంది",
|
|
||||||
"memberFallbackName": "సభ్యుడు",
|
|
||||||
"memberSubtitleEmpty": "మీ పెట్టుబడులను నిర్వహించడానికి చిట్ ఫండ్లో చేరండి.",
|
|
||||||
"memberSubtitleHasGroups": "మీ చిట్ ఫండ్ పెట్టుబడులు మరియు చెల్లింపులను ట్రాక్ చేయండి.",
|
|
||||||
"navHome": "హోమ్",
|
|
||||||
"navPayments": "చెల్లింపులు",
|
|
||||||
"navNotifications": "నోటిఫికేషన్లు",
|
|
||||||
"navProfile": "ప్రొఫైల్",
|
|
||||||
"memberEmptyChitTitle": "ఇంకా చిట్ ఫండ్లు లేవు",
|
|
||||||
"memberEmptyChitBody": "మీరు ఇంకా ఏ చిట్ ఫండ్లోనూ చేరలేదు.\nప్రారంభించడానికి మీ మేనేజర్ను సంప్రదించండి!",
|
|
||||||
"memberHowToStartTitle": "ఎలా ప్రారంభించాలి?",
|
|
||||||
"memberHowToStartBody": "1. మీ మేనేజర్ మిమ్మల్ని చిట్ గ్రూప్కు జోడిస్తారు\n2. మీకు నోటిఫికేషన్ వస్తుంది\n3. ఇక్కడ మీ చెల్లింపులను నిర్వహించండి!",
|
|
||||||
"unnamedGroupLong": "పేరులేని గ్రూప్",
|
|
||||||
"labelTotalValue": "మొత్తం విలువ",
|
|
||||||
"labelDuration": "కాలపరిమితి",
|
|
||||||
"monthsSuffix": "నెలలు",
|
|
||||||
"labelInstallment": "వాయిదా",
|
|
||||||
"labelStatus": "స్థితి",
|
|
||||||
"groupStatusPending": "పెండింగ్",
|
|
||||||
"payNowButton": "ఇప్పుడు చెల్లించు",
|
|
||||||
"detailsButton": "వివరాలు",
|
|
||||||
"memberInfoNotFound": "సభ్య సమాచారం కనుగొనబడలేదు"
|
|
||||||
}
|
|
||||||
|
|
@ -56,8 +56,8 @@ dependencies:
|
||||||
# WebSocket for real-time updates
|
# WebSocket for real-time updates
|
||||||
web_socket_channel: ^2.4.0
|
web_socket_channel: ^2.4.0
|
||||||
|
|
||||||
# Date and time (version must satisfy flutter_localizations’ intl pin — e.g. 0.20.2 on current stable Flutter)
|
# Date and Time
|
||||||
intl: 0.20.2
|
intl: ^0.19.0
|
||||||
|
|
||||||
# Charts and Data Visualization
|
# Charts and Data Visualization
|
||||||
fl_chart: ^0.66.0
|
fl_chart: ^0.66.0
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,30 @@
|
||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:luckychit/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Smoke: MaterialApp renders', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
// Build our app and trigger a frame.
|
||||||
const MaterialApp(
|
await tester.pumpWidget(const MyApp());
|
||||||
home: Scaffold(
|
|
||||||
body: Center(child: Text('LuckyChit')),
|
// Verify that our counter starts at 0.
|
||||||
),
|
expect(find.text('0'), findsOneWidget);
|
||||||
),
|
expect(find.text('1'), findsNothing);
|
||||||
);
|
|
||||||
expect(find.text('LuckyChit'), findsOneWidget);
|
// Tap the '+' icon and trigger a frame.
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify that our counter has incremented.
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(find.text('1'), findsOneWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Merges lib/l10n/app_{en,te}.arb with app_{en,te}_more.arb (in-memory) and
|
|
||||||
* writes lib/l10n/app_localizations.dart, app_localizations_en.dart, app_localizations_te.dart.
|
|
||||||
*
|
|
||||||
* Run from package root: node tool/sync_l10n.mjs
|
|
||||||
*/
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
const root = path.join(__dirname, "..");
|
|
||||||
const l10nDir = path.join(root, "lib", "l10n");
|
|
||||||
|
|
||||||
function loadArb(name) {
|
|
||||||
const p = path.join(l10nDir, name);
|
|
||||||
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(base, extra) {
|
|
||||||
return { ...base, ...extra };
|
|
||||||
}
|
|
||||||
|
|
||||||
function messageKeys(arb) {
|
|
||||||
return Object.keys(arb).filter((k) => !k.startsWith("@") && k !== "@@locale");
|
|
||||||
}
|
|
||||||
|
|
||||||
function placeholdersFor(arb, key) {
|
|
||||||
const meta = arb[`@${key}`];
|
|
||||||
if (!meta || !meta.placeholders) return null;
|
|
||||||
return Object.keys(meta.placeholders);
|
|
||||||
}
|
|
||||||
|
|
||||||
function dartEscapeSingle(s) {
|
|
||||||
return s
|
|
||||||
.replace(/\\/g, "\\\\")
|
|
||||||
.replace(/\r\n/g, "\n")
|
|
||||||
.replace(/\n/g, "\\n")
|
|
||||||
.replace(/'/g, "\\'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build Dart string literal expr from ICU-ish template (e.g. "Hi, {name}!"). */
|
|
||||||
function dartReturnExpr(template, params) {
|
|
||||||
let d = template;
|
|
||||||
for (const p of params) {
|
|
||||||
d = d.split(`{${p}}`).join(`$${p}`);
|
|
||||||
}
|
|
||||||
return `'${dartEscapeSingle(d)}'`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeLocalizations(en, te) {
|
|
||||||
const keys = [...new Set([...messageKeys(en), ...messageKeys(te)])].sort();
|
|
||||||
|
|
||||||
const abstractDecls = [];
|
|
||||||
const enImpl = [];
|
|
||||||
const teImpl = [];
|
|
||||||
|
|
||||||
for (const key of keys) {
|
|
||||||
const ph = placeholdersFor(en, key) ?? placeholdersFor(te, key);
|
|
||||||
if (ph && ph.length) {
|
|
||||||
const args = ph.map((p) => `String ${p}`).join(", ");
|
|
||||||
abstractDecls.push(` String ${key}(${args});`);
|
|
||||||
const tplEn = en[key];
|
|
||||||
const tplTe = te[key] ?? tplEn;
|
|
||||||
enImpl.push(` @override\n String ${key}(${args}) {\n return ${dartReturnExpr(tplEn, ph)};\n }`);
|
|
||||||
teImpl.push(` @override\n String ${key}(${args}) {\n return ${dartReturnExpr(tplTe, ph)};\n }`);
|
|
||||||
} else {
|
|
||||||
abstractDecls.push(` String get ${key};`);
|
|
||||||
const vEn = en[key] ?? "";
|
|
||||||
const vTe = te[key] ?? vEn;
|
|
||||||
enImpl.push(` @override\n String get ${key} => '${dartEscapeSingle(vEn)}';`);
|
|
||||||
teImpl.push(` @override\n String get ${key} => '${dartEscapeSingle(vTe)}';`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = `// GENERATED FILE — do not edit by hand.
|
|
||||||
// Source: lib/l10n/app_en.arb + app_en_more.arb (merged). Regenerate: node tool/sync_l10n.mjs
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const main = `${header}import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
||||||
import 'app_localizations_en.dart';
|
|
||||||
import 'app_localizations_te.dart';
|
|
||||||
|
|
||||||
abstract class AppLocalizations {
|
|
||||||
AppLocalizations(String locale) : localeName = locale;
|
|
||||||
|
|
||||||
final String localeName;
|
|
||||||
|
|
||||||
static AppLocalizations? of(BuildContext context) {
|
|
||||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
|
|
||||||
|
|
||||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
|
||||||
<LocalizationsDelegate<dynamic>>[
|
|
||||||
delegate,
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
];
|
|
||||||
|
|
||||||
static const List<Locale> supportedLocales = <Locale>[
|
|
||||||
Locale('en'),
|
|
||||||
Locale('te'),
|
|
||||||
];
|
|
||||||
|
|
||||||
${abstractDecls.join("\n\n")}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
|
||||||
const _AppLocalizationsDelegate();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<AppLocalizations> load(Locale locale) {
|
|
||||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool isSupported(Locale locale) =>
|
|
||||||
<String>['en', 'te'].contains(locale.languageCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
|
||||||
switch (locale.languageCode) {
|
|
||||||
case 'en':
|
|
||||||
return AppLocalizationsEn();
|
|
||||||
case 'te':
|
|
||||||
return AppLocalizationsTe();
|
|
||||||
}
|
|
||||||
throw FlutterError(
|
|
||||||
'AppLocalizations.delegate failed to load unsupported locale "\$locale".',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const enFile = `${header}import 'app_localizations.dart';
|
|
||||||
|
|
||||||
class AppLocalizationsEn extends AppLocalizations {
|
|
||||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
|
||||||
|
|
||||||
${enImpl.join("\n\n")}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const teFile = `${header}import 'app_localizations.dart';
|
|
||||||
|
|
||||||
class AppLocalizationsTe extends AppLocalizations {
|
|
||||||
AppLocalizationsTe([String locale = 'te']) : super(locale);
|
|
||||||
|
|
||||||
${teImpl.join("\n\n")}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(l10nDir, "app_localizations.dart"), main);
|
|
||||||
fs.writeFileSync(path.join(l10nDir, "app_localizations_en.dart"), enFile);
|
|
||||||
fs.writeFileSync(path.join(l10nDir, "app_localizations_te.dart"), teFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseEn = loadArb("app_en.arb");
|
|
||||||
const baseTe = loadArb("app_te.arb");
|
|
||||||
const moreEn = loadArb("app_en_more.arb");
|
|
||||||
const moreTe = loadArb("app_te_more.arb");
|
|
||||||
|
|
||||||
const en = merge(baseEn, moreEn);
|
|
||||||
const te = merge(baseTe, moreTe);
|
|
||||||
|
|
||||||
writeLocalizations(en, te);
|
|
||||||
console.log("Wrote app_localizations*.dart from merged ARBs.");
|
|
||||||
|
|
@ -1,147 +1,80 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
#
|
|
||||||
# LuckyChit deployment — backend (Node/PM2) + frontend (Flutter web + PM2).
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./scripts/deploy.sh # backend + frontend
|
|
||||||
# ./scripts/deploy.sh backend # API only
|
|
||||||
# ./scripts/deploy.sh frontend # Flutter web only
|
|
||||||
# ./scripts/deploy.sh backend --force # e.g. wipe node_modules / flutter clean
|
|
||||||
#
|
|
||||||
# Env (optional):
|
|
||||||
# LUCKYCHIT_ROOT Repo root (default: /home/luckychit/apps/chitfund)
|
|
||||||
# DEPLOY_BRANCH Git branch to fast-forward to (default: prod_i8n)
|
|
||||||
|
|
||||||
set -eo pipefail
|
# LuckyChit Unified Deployment Script
|
||||||
|
# Usage: ./deploy.sh [backend|frontend|--force]
|
||||||
|
|
||||||
PROJECT_DIR="${LUCKYCHIT_ROOT:-/home/luckychit/apps/chitfund}"
|
set -e
|
||||||
BRANCH="${DEPLOY_BRANCH:-prod_i8n}"
|
|
||||||
|
PROJECT_DIR="/home/luckychit/apps/chitfund"
|
||||||
FORCE_REBUILD=false
|
FORCE_REBUILD=false
|
||||||
TARGET=""
|
|
||||||
|
|
||||||
usage() {
|
# Check for force flag
|
||||||
echo "Usage: $0 [backend|frontend] [--force]"
|
if [ "$1" == "--force" ] || [ "$2" == "--force" ]; then
|
||||||
echo " (no args) deploy backend and frontend"
|
FORCE_REBUILD=true
|
||||||
echo " backend deploy Node API only (pm2: luckychit-api)"
|
|
||||||
echo " frontend build Flutter web + restart static server (pm2: luckychit-web)"
|
|
||||||
echo " --force aggressive clean (backend: rm node_modules; frontend: flutter clean)"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in "$@"; do
|
|
||||||
case "$arg" in
|
|
||||||
--force)
|
|
||||||
FORCE_REBUILD=true
|
|
||||||
;;
|
|
||||||
backend|frontend)
|
|
||||||
if [ -n "$TARGET" ] && [ "$TARGET" != "$arg" ]; then
|
|
||||||
echo "Error: specify only one of backend or frontend."
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
TARGET="$arg"
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown argument: $arg"
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
run_backend() {
|
|
||||||
[ "$TARGET" = "" ] || [ "$TARGET" = "backend" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
run_frontend() {
|
|
||||||
[ "$TARGET" = "" ] || [ "$TARGET" = "frontend" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
|
|
||||||
echo "LuckyChit deployment"
|
|
||||||
echo "===================="
|
|
||||||
echo "Root: $PROJECT_DIR"
|
|
||||||
echo "Branch: $BRANCH"
|
|
||||||
echo "Target: ${TARGET:-backend+frontend} force=$FORCE_REBUILD"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
|
||||||
echo "Warning: running as root. Prefer a normal user so Flutter/npm caches stay consistent."
|
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Pulling latest code..."
|
cd $PROJECT_DIR
|
||||||
git fetch origin "$BRANCH"
|
|
||||||
if ! git rev-parse --verify "refs/remotes/origin/$BRANCH" >/dev/null 2>&1; then
|
echo "🚀 LuckyChit Deployment"
|
||||||
echo "Error: origin/$BRANCH not found. Fetch failed or branch missing on remote."
|
echo "======================="
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
git checkout "$BRANCH"
|
|
||||||
git merge --ff-only "origin/$BRANCH"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if run_backend; then
|
# Git pull
|
||||||
echo "Deploying backend..."
|
echo "📥 Pulling latest code..."
|
||||||
cd "$PROJECT_DIR/backend"
|
git stash 2>/dev/null || true
|
||||||
|
git pull origin prodnew
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [ "$FORCE_REBUILD" = true ]; then
|
# Deploy backend
|
||||||
echo "Force rebuild: removing node_modules and lockfile..."
|
if [ "$1" == "" ] || [ "$1" == "backend" ] || [ "$1" == "--force" ]; then
|
||||||
rm -rf node_modules package-lock.json
|
echo "🔧 Deploying Backend..."
|
||||||
fi
|
cd $PROJECT_DIR/backend
|
||||||
|
|
||||||
if [ -f package-lock.json ]; then
|
if [ "$FORCE_REBUILD" = true ]; then
|
||||||
npm ci
|
echo "🗑️ Force rebuild: Removing node_modules..."
|
||||||
else
|
rm -rf node_modules package-lock.json
|
||||||
|
fi
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
fi
|
pm2 restart luckychit-api
|
||||||
|
echo "✅ Backend deployed"
|
||||||
if pm2 describe luckychit-api >/dev/null 2>&1; then
|
echo ""
|
||||||
pm2 restart luckychit-api --update-env
|
|
||||||
else
|
|
||||||
pm2 start ecosystem.config.js --env production
|
|
||||||
fi
|
|
||||||
echo "Backend done."
|
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if run_frontend; then
|
# Deploy frontend
|
||||||
echo "Deploying frontend (Flutter web)..."
|
if [ "$1" == "" ] || [ "$1" == "frontend" ] || [ "$1" == "--force" ]; then
|
||||||
cd "$PROJECT_DIR/luckychit"
|
echo "🎨 Deploying Frontend..."
|
||||||
|
cd $PROJECT_DIR/luckychit
|
||||||
if [ "$FORCE_REBUILD" = true ]; then
|
|
||||||
echo "Force rebuild: flutter clean..."
|
if [ "$FORCE_REBUILD" = true ]; then
|
||||||
flutter clean
|
echo "🗑️ Force rebuild: Cleaning Flutter cache..."
|
||||||
rm -rf .dart_tool build
|
flutter clean
|
||||||
fi
|
rm -rf .dart_tool build
|
||||||
|
fi
|
||||||
flutter pub get
|
|
||||||
|
flutter pub get
|
||||||
if [ -f tool/sync_l10n.mjs ]; then
|
|
||||||
node tool/sync_l10n.mjs
|
BUILD_NUMBER=$(date +%s)
|
||||||
fi
|
flutter build web --release --pwa-strategy=none --build-number=$BUILD_NUMBER
|
||||||
|
echo "📦 Build version: $BUILD_NUMBER"
|
||||||
BUILD_NUMBER=$(date +%s)
|
|
||||||
flutter build web --release --pwa-strategy=none --build-number="$BUILD_NUMBER"
|
pm2 restart luckychit-frontend
|
||||||
echo "Build number: $BUILD_NUMBER"
|
echo "✅ Frontend deployed"
|
||||||
|
echo ""
|
||||||
# Name must match luckychit/ecosystem.config.js (app name: luckychit-web)
|
|
||||||
if pm2 describe luckychit-web >/dev/null 2>&1; then
|
|
||||||
pm2 restart luckychit-web --update-env
|
|
||||||
else
|
|
||||||
pm2 start ecosystem.config.js --env production
|
|
||||||
fi
|
|
||||||
echo "Frontend done."
|
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "PM2 status:"
|
# Status
|
||||||
|
echo "📊 PM2 Status:"
|
||||||
pm2 status
|
pm2 status
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Deployment complete."
|
echo "✅ Deployment complete!"
|
||||||
echo "Smoke tests:"
|
echo ""
|
||||||
echo " curl -sf http://localhost:3000/health || curl -sf http://localhost:3000/api/health"
|
echo "🧪 Test:"
|
||||||
echo " curl -sfI http://localhost:8080/"
|
echo " Backend: curl http://localhost:3000/health"
|
||||||
echo "Logs: pm2 logs --lines 30"
|
echo " Frontend: curl http://localhost:8080"
|
||||||
|
echo " Domain: https://chitfund.deepteklabs.com"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Check logs: pm2 logs --lines 20"
|
||||||
|
echo "💡 Clear browser cache: Ctrl + Shift + R"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue