diff --git a/assets/icons/addPic.svg b/assets/icons/addPic.svg
new file mode 100644
index 0000000..45e58cd
--- /dev/null
+++ b/assets/icons/addPic.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/calendar-search.svg b/assets/icons/calendar-search.svg
new file mode 100644
index 0000000..bc23c10
--- /dev/null
+++ b/assets/icons/calendar-search.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/icons/call-calling.svg b/assets/icons/call-calling.svg
new file mode 100644
index 0000000..4f40fd1
--- /dev/null
+++ b/assets/icons/call-calling.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/document-text.svg b/assets/icons/document-text.svg
new file mode 100644
index 0000000..d35499c
--- /dev/null
+++ b/assets/icons/document-text.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/info-circle.svg b/assets/icons/info-circle.svg
new file mode 100644
index 0000000..c828515
--- /dev/null
+++ b/assets/icons/info-circle.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/radar-2.svg b/assets/icons/radar-2.svg
new file mode 100644
index 0000000..b3c46e3
--- /dev/null
+++ b/assets/icons/radar-2.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/emptyShop.svg b/assets/images/emptyShop.svg
new file mode 100644
index 0000000..966302c
--- /dev/null
+++ b/assets/images/emptyShop.svg
@@ -0,0 +1,271 @@
+
diff --git a/assets/images/shoppAdded.png b/assets/images/shoppAdded.png
new file mode 100644
index 0000000..f9b0e74
Binary files /dev/null and b/assets/images/shoppAdded.png differ
diff --git a/lib/core/services/token_storage_service.dart b/lib/core/services/token_storage_service.dart
new file mode 100644
index 0000000..5eba95d
--- /dev/null
+++ b/lib/core/services/token_storage_service.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+
+class TokenStorageService {
+ final _storage = const FlutterSecureStorage();
+
+ static const _accessTokenKey = 'access_token';
+ static const _refreshTokenKey = 'refresh_token';
+
+ Future saveTokens({
+ required String accessToken,
+ required String refreshToken,
+ }) async {
+ await _storage.write(key: _accessTokenKey, value: accessToken);
+ await _storage.write(key: _refreshTokenKey, value: refreshToken);
+ }
+
+ Future getAccessToken() async {
+ return await _storage.read(key: _accessTokenKey);
+ }
+
+ Future getRefreshToken() async {
+ return await _storage.read(key: _refreshTokenKey);
+ }
+
+ Future deleteAllTokens() async {
+ await _storage.delete(key: _accessTokenKey);
+ await _storage.delete(key: _refreshTokenKey);
+ }
+}
\ No newline at end of file
diff --git a/lib/core/utils/logging_interceptor.dart b/lib/core/utils/logging_interceptor.dart
new file mode 100644
index 0000000..2cf0f4a
--- /dev/null
+++ b/lib/core/utils/logging_interceptor.dart
@@ -0,0 +1,42 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+
+class LoggingInterceptor extends Interceptor {
+ @override
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
+ if (kDebugMode) {
+ print('--- API REQUEST ---');
+ print('METHOD: ${options.method}');
+ print('URL: ${options.uri}');
+ print('HEADERS: ${options.headers}');
+ print('BODY: ${options.data}');
+ print('-------------------');
+ }
+ super.onRequest(options, handler);
+ }
+
+ @override
+ void onResponse(Response response, ResponseInterceptorHandler handler) {
+ if (kDebugMode) {
+ print('--- API RESPONSE ---');
+ print('STATUS_CODE: ${response.statusCode}');
+ print('URL: ${response.requestOptions.uri}');
+ print('DATA: ${response.data}');
+ print('--------------------');
+ }
+ super.onResponse(response, handler);
+ }
+
+ @override
+ void onError(DioException err, ErrorInterceptorHandler handler) {
+ if (kDebugMode) {
+ print('--- API ERROR ---');
+ print('STATUS_CODE: ${err.response?.statusCode}');
+ print('URL: ${err.requestOptions.uri}');
+ print('ERROR: ${err.error}');
+ print('RESPONSE_DATA: ${err.response?.data}');
+ print('-----------------');
+ }
+ super.onError(err, handler);
+ }
+}
\ No newline at end of file
diff --git a/lib/domain/entities/category_entity.dart b/lib/domain/entities/category_entity.dart
new file mode 100644
index 0000000..64f57b3
--- /dev/null
+++ b/lib/domain/entities/category_entity.dart
@@ -0,0 +1,7 @@
+class CategoryEntity {
+ final String id;
+ final String name;
+ final String emoji;
+
+ CategoryEntity({required this.id, required this.name, this.emoji = ''});
+}
\ No newline at end of file
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index 59bb631..90d88bc 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -66,6 +66,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/addImg.svg
String get addImg => 'assets/icons/addImg.svg';
+ /// File path: assets/icons/addPic.svg
+ String get addPic => 'assets/icons/addPic.svg';
+
/// File path: assets/icons/appbar2.svg
String get appbar2 => 'assets/icons/appbar2.svg';
@@ -87,6 +90,12 @@ class $AssetsIconsGen {
/// File path: assets/icons/backArrow.svg
String get backArrow => 'assets/icons/backArrow.svg';
+ /// File path: assets/icons/calendar-search.svg
+ String get calendarSearch => 'assets/icons/calendar-search.svg';
+
+ /// File path: assets/icons/call-calling.svg
+ String get callCalling => 'assets/icons/call-calling.svg';
+
/// File path: assets/icons/camera.svg
String get camera => 'assets/icons/camera.svg';
@@ -111,6 +120,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/discount-shape.svg
String get discountShape => 'assets/icons/discount-shape.svg';
+ /// File path: assets/icons/document-text.svg
+ String get documentText => 'assets/icons/document-text.svg';
+
/// File path: assets/icons/edit-02.svg
String get edit02 => 'assets/icons/edit-02.svg';
@@ -129,6 +141,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/global-search.svg
String get globalSearch => 'assets/icons/global-search.svg';
+ /// File path: assets/icons/info-circle.svg
+ String get infoCircle => 'assets/icons/info-circle.svg';
+
/// File path: assets/icons/kafsh.svg
String get kafsh => 'assets/icons/kafsh.svg';
@@ -147,6 +162,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/pooshak.svg
String get pooshak => 'assets/icons/pooshak.svg';
+ /// File path: assets/icons/radar-2.svg
+ String get radar2 => 'assets/icons/radar-2.svg';
+
/// File path: assets/icons/receipt-disscount.svg
String get receiptDisscount => 'assets/icons/receipt-disscount.svg';
@@ -227,6 +245,7 @@ class $AssetsIconsGen {
tshirt,
vector,
addImg,
+ addPic,
appbar2,
arayesh,
arrowDown,
@@ -234,6 +253,8 @@ class $AssetsIconsGen {
arrowUp,
back,
backArrow,
+ calendarSearch,
+ callCalling,
camera,
cardPos,
cinama,
@@ -242,18 +263,21 @@ class $AssetsIconsGen {
coffeeshop,
digital,
discountShape,
+ documentText,
edit02,
edit,
error,
fastfood,
galleryAdd,
globalSearch,
+ infoCircle,
kafsh,
location,
logo,
map,
notification,
pooshak,
+ radar2,
receiptDisscount,
resturan,
routing,
@@ -307,6 +331,13 @@ class $AssetsImagesGen {
/// File path: assets/images/empty home.svg
String get emptyHome => 'assets/images/empty home.svg';
+ /// File path: assets/images/emptyShop.svg
+ String get emptyShop => 'assets/images/emptyShop.svg';
+
+ /// File path: assets/images/shoppAdded.png
+ AssetGenImage get shoppAdded =>
+ const AssetGenImage('assets/images/shoppAdded.png');
+
/// File path: assets/images/userinfo.png
AssetGenImage get userinfo =>
const AssetGenImage('assets/images/userinfo.png');
@@ -320,6 +351,8 @@ class $AssetsImagesGen {
rectangle3,
rectangle4,
emptyHome,
+ emptyShop,
+ shoppAdded,
userinfo,
];
}
diff --git a/lib/presentation/auth/bloc/auth_bloc.dart b/lib/presentation/auth/bloc/auth_bloc.dart
index 7ddc22f..2e68cb0 100644
--- a/lib/presentation/auth/bloc/auth_bloc.dart
+++ b/lib/presentation/auth/bloc/auth_bloc.dart
@@ -1,31 +1,72 @@
-
-
import 'package:bloc/bloc.dart';
+import 'package:business_panel/core/services/token_storage_service.dart';
+import 'package:dio/dio.dart';
+import 'package:business_panel/core/utils/logging_interceptor.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc {
+
+ late final Dio _dio;
+ final TokenStorageService _tokenStorage = TokenStorageService();
AuthBloc() : super(AuthInitial()) {
+ _dio = Dio();
+ _dio.interceptors.add(LoggingInterceptor());
+
on((event, emit) async {
emit(AuthLoading());
- await Future.delayed(const Duration(seconds: 1));
- if (event.phoneNumber.isNotEmpty) {
- emit(AuthCodeSentSuccess());
- } else {
- emit(AuthFailure('شماره موبایل معتبر نیست.'));
+ emit(AuthLoading());
+ try {
+ final response = await _dio.post(
+ 'https://fartak.liara.run/login/sendcode',
+ data: {
+ 'Phone': event.phoneNumber,
+ 'Code': event.countryCode,
+ },
+ );
+
+ if (response.statusCode == 200) {
+ emit(AuthCodeSentSuccess());
+ } else {
+ emit(AuthFailure(response.data['message'] ?? 'خطایی رخ داد'));
+ }
+ } on DioException catch (e) {
+ emit(AuthFailure(e.response?.data['message'] ?? 'خطای شبکه'));
}
});
on((event, emit) async {
emit(AuthLoading());
- await Future.delayed(const Duration(seconds: 1));
- if (event.otp == '12345') {
- emit(AuthVerified());
- } else {
- emit(AuthFailure('کد تایید صحیح نمیباشد.'));
+ try {
+ final response = await _dio.post(
+ 'https://fartak.liara.run/login/getcode',
+ data: {
+ 'Phone': event.phoneNumber,
+ 'Code': event.countryCode,
+ 'OTP': event.otp,
+ },
+ );
+
+ if (response.statusCode == 200 && response.data['data']['accessToken'] != null) {
+
+ final accessToken = response.data['data']['accessToken'];
+ final refreshToken = response.data['data']['refreshToken'];
+
+ await _tokenStorage.saveTokens(
+ accessToken: accessToken,
+ refreshToken: refreshToken,
+ );
+
+ emit(AuthVerified());
+ } else {
+ emit(AuthFailure(response.data['message'] ?? 'کد تایید صحیح نمیباشد.'));
+ }
+ } on DioException catch (e) {
+ emit(AuthFailure(e.response?.data['message'] ?? 'خطای شبکه'));
}
});
+
on((event, emit) async {
emit(AuthLoading());
await Future.delayed(const Duration(milliseconds: 500));
@@ -37,4 +78,4 @@ class AuthBloc extends Bloc {
}
});
}
-}
+}
\ No newline at end of file
diff --git a/lib/presentation/auth/bloc/auth_event.dart b/lib/presentation/auth/bloc/auth_event.dart
index adf1b2b..a21dcc0 100644
--- a/lib/presentation/auth/bloc/auth_event.dart
+++ b/lib/presentation/auth/bloc/auth_event.dart
@@ -1,18 +1,20 @@
-
part of 'auth_bloc.dart';
abstract class AuthEvent {}
class SendOTPEvent extends AuthEvent {
final String phoneNumber;
+ final String countryCode;
- SendOTPEvent({required this.phoneNumber});
+ SendOTPEvent({required this.phoneNumber, required this.countryCode});
}
class VerifyOTPEvent extends AuthEvent {
final String otp;
+ final String phoneNumber;
+ final String countryCode;
- VerifyOTPEvent({required this.otp});
+ VerifyOTPEvent({required this.otp, required this.phoneNumber, required this.countryCode});
}
class SaveUserInfoEvent extends AuthEvent {
@@ -20,4 +22,4 @@ class SaveUserInfoEvent extends AuthEvent {
final String gender;
SaveUserInfoEvent({required this.name, required this.gender});
-}
+}
\ No newline at end of file
diff --git a/lib/presentation/discount/bloc/discount_bloc.dart b/lib/presentation/discount/bloc/discount_bloc.dart
new file mode 100644
index 0000000..b040851
--- /dev/null
+++ b/lib/presentation/discount/bloc/discount_bloc.dart
@@ -0,0 +1,49 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'discount_event.dart';
+import 'discount_state.dart';
+
+class DiscountBloc extends Bloc {
+ DiscountBloc() : super(const DiscountState()) {
+ on((event, emit) {
+ List updatedImages = List.from(state.productImages);
+ if (updatedImages.length > event.index) {
+ updatedImages[event.index] = event.imagePath;
+ } else {
+ updatedImages.add(event.imagePath);
+ }
+ emit(state.copyWith(productImages: updatedImages));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(productName: event.name));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(discountType: event.type));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(description: event.description));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(startDate: event.startDate, endDate: event.endDate));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(startTime: event.startTime, endTime: event.endTime));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(price: event.price));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(discountedPrice: event.price));
+ });
+
+ on((event, emit) {
+ emit(state.copyWith(notificationRadius: event.radius));
+ });
+ }
+}
\ No newline at end of file
diff --git a/lib/presentation/discount/bloc/discount_event.dart b/lib/presentation/discount/bloc/discount_event.dart
new file mode 100644
index 0000000..1ff9850
--- /dev/null
+++ b/lib/presentation/discount/bloc/discount_event.dart
@@ -0,0 +1,52 @@
+
+abstract class DiscountEvent {}
+
+class ProductImageAdded extends DiscountEvent {
+ final String imagePath;
+ final int index;
+ ProductImageAdded(this.imagePath, this.index);
+}
+
+class ProductNameChanged extends DiscountEvent {
+ final String name;
+ ProductNameChanged(this.name);
+}
+
+class DiscountTypeChanged extends DiscountEvent {
+ final String type;
+ DiscountTypeChanged(this.type);
+}
+
+class DescriptionChanged extends DiscountEvent {
+ final String description;
+ DescriptionChanged(this.description);
+}
+
+class ValidityDateChanged extends DiscountEvent {
+ final DateTime startDate;
+ final DateTime endDate;
+ ValidityDateChanged({required this.startDate, required this.endDate});
+}
+
+class TimeRangeChanged extends DiscountEvent {
+ final String startTime;
+ final String endTime;
+ TimeRangeChanged({required this.startTime, required this.endTime});
+}
+
+class PriceChanged extends DiscountEvent {
+ final String price;
+ PriceChanged(this.price);
+}
+
+class DiscountedPriceChanged extends DiscountEvent {
+ final String price;
+ DiscountedPriceChanged(this.price);
+}
+
+class NotificationRadiusChanged extends DiscountEvent {
+ final double radius;
+ NotificationRadiusChanged(this.radius);
+}
+
+class SubmitDiscount extends DiscountEvent {}
\ No newline at end of file
diff --git a/lib/presentation/discount/bloc/discount_state.dart b/lib/presentation/discount/bloc/discount_state.dart
new file mode 100644
index 0000000..9c48637
--- /dev/null
+++ b/lib/presentation/discount/bloc/discount_state.dart
@@ -0,0 +1,87 @@
+import 'package:equatable/equatable.dart';
+
+class DiscountState extends Equatable {
+ final List productImages;
+ final String productName;
+ final String? discountType;
+ final String description;
+ final DateTime? startDate;
+ final DateTime? endDate;
+ final String? startTime;
+ final String? endTime;
+ final String price;
+ final String discountedPrice;
+ final double notificationRadius;
+ final bool isSubmitting;
+ final bool isSuccess;
+ final String? errorMessage;
+
+ const DiscountState({
+ this.productImages = const [],
+ this.productName = '',
+ this.discountType,
+ this.description = '',
+ this.startDate,
+ this.endDate,
+ this.startTime,
+ this.endTime,
+ this.price = '',
+ this.discountedPrice = '',
+ this.notificationRadius = 0.0,
+ this.isSubmitting = false,
+ this.isSuccess = false,
+ this.errorMessage,
+ });
+
+ DiscountState copyWith({
+ List? productImages,
+ String? productName,
+ String? discountType,
+ String? description,
+ DateTime? startDate,
+ DateTime? endDate,
+ String? startTime,
+ String? endTime,
+ String? price,
+ String? discountedPrice,
+ double? notificationRadius,
+ bool? isSubmitting,
+ bool? isSuccess,
+ String? errorMessage,
+ }) {
+ return DiscountState(
+ productImages: productImages ?? this.productImages,
+ productName: productName ?? this.productName,
+ discountType: discountType ?? this.discountType,
+ description: description ?? this.description,
+ startDate: startDate ?? this.startDate,
+ endDate: endDate ?? this.endDate,
+ startTime: startTime ?? this.startTime,
+ endTime: endTime ?? this.endTime,
+ price: price ?? this.price,
+ discountedPrice: discountedPrice ?? this.discountedPrice,
+ notificationRadius: notificationRadius ?? this.notificationRadius,
+ isSubmitting: isSubmitting ?? this.isSubmitting,
+ isSuccess: isSuccess ?? this.isSuccess,
+ errorMessage: errorMessage ?? this.errorMessage,
+ );
+ }
+
+ @override
+ List