s
This commit is contained in:
parent
a3cf294070
commit
33c15b1a5f
@ -13,4 +13,8 @@ class ApiEndpoints {
|
||||
static const signup = '/auth/signup';
|
||||
static const forgotPassword = '/auth/forgot-password';
|
||||
static const userDetails = '/auth/users/';
|
||||
|
||||
///Turn14
|
||||
static const turn14Save = '/auth/turn14/save';
|
||||
static const turn14Status = '/auth/turn14/status';
|
||||
}
|
||||
|
||||
@ -3,6 +3,9 @@ import 'package:autos/presentation/screens/auth/forgot_password_screen.dart';
|
||||
import 'package:autos/presentation/screens/auth/login_screen.dart';
|
||||
import 'package:autos/presentation/screens/auth/sign_up_screen.dart';
|
||||
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
|
||||
import 'package:autos/presentation/screens/ebay/ebay_screen.dart';
|
||||
import 'package:autos/presentation/screens/store/create_location_screen.dart';
|
||||
import 'package:autos/presentation/screens/store/store.dart';
|
||||
import 'package:autos/presentation/screens/turn14_screen/turn14_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -45,6 +48,15 @@ class AppRouter {
|
||||
case AppRoutePaths.turn14:
|
||||
return slideRoute(const Turn14Screen());
|
||||
|
||||
case AppRoutePaths.ebay:
|
||||
return slideRoute(EbayScreen());
|
||||
|
||||
case AppRoutePaths.store:
|
||||
return slideRoute(StoreScreen());
|
||||
|
||||
case AppRoutePaths.createStoreLocation:
|
||||
return slideRoute(CreateLocationScreen());
|
||||
|
||||
default:
|
||||
return _defaultFallback(settings);
|
||||
}
|
||||
|
||||
@ -4,4 +4,7 @@ class AppRoutePaths {
|
||||
static const forgotPassword = '/forgotPassword';
|
||||
static const dashboard = '/dashboard';
|
||||
static const turn14 = '/turn14';
|
||||
static const ebay = '/ebay';
|
||||
static const store = '/store';
|
||||
static const createStoreLocation = '/createStoreLocation';
|
||||
}
|
||||
|
||||
@ -59,8 +59,8 @@ class SideMenu extends ConsumerWidget {
|
||||
// --- INTEGRATIONS ---
|
||||
_sectionHeader("INTEGRATIONS"),
|
||||
_menuItem(context, "⚡", "Turn14", AppRoutePaths.turn14),
|
||||
_menuItem(context, "🛍️", "eBay", 'ebay'),
|
||||
_menuItem(context, "🛒", "Store", 'store'),
|
||||
_menuItem(context, "🛍️", "eBay", AppRoutePaths.ebay),
|
||||
_menuItem(context, "🛒", "Store", AppRoutePaths.store),
|
||||
|
||||
// --- MANAGE ---
|
||||
_sectionHeader("MANAGE"),
|
||||
|
||||
98
lib/data/models/turn14_model.dart
Normal file
98
lib/data/models/turn14_model.dart
Normal file
@ -0,0 +1,98 @@
|
||||
import 'dart:convert';
|
||||
import 'package:autos/domain/entities/turn14.dart';
|
||||
|
||||
class Turn14Response {
|
||||
final String code;
|
||||
final String message;
|
||||
final String userId;
|
||||
final String accessToken;
|
||||
|
||||
const Turn14Response({
|
||||
required this.code,
|
||||
required this.message,
|
||||
required this.userId,
|
||||
required this.accessToken,
|
||||
});
|
||||
|
||||
factory Turn14Response.fromJson(Map<String, dynamic> json) {
|
||||
return Turn14Response(
|
||||
code: json['code'] ?? '',
|
||||
message: json['message'] ?? '',
|
||||
userId: json['userid'] ?? '',
|
||||
accessToken: json['access_token'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'code': code,
|
||||
'message': message,
|
||||
'userid': userId,
|
||||
'access_token': accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert model → raw JSON string
|
||||
String toRawJson() => jsonEncode(toJson());
|
||||
|
||||
/// Convert raw JSON string → model
|
||||
factory Turn14Response.fromRawJson(String raw) =>
|
||||
Turn14Response.fromJson(jsonDecode(raw));
|
||||
|
||||
/// Convert Response → Domain Entity
|
||||
Turn14Entity toEntity() {
|
||||
return Turn14Entity(
|
||||
code: code,
|
||||
message: message,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Turn14Response(code: $code, message: $message, userId: $userId, accessToken: $accessToken)';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Turn14StatusModel {
|
||||
final String userId;
|
||||
final bool hasCredentials;
|
||||
final String? clientId;
|
||||
final String? clientSecret;
|
||||
final String? accessToken;
|
||||
final String? expiresIn;
|
||||
|
||||
Turn14StatusModel({
|
||||
required this.userId,
|
||||
required this.hasCredentials,
|
||||
this.clientId,
|
||||
this.clientSecret,
|
||||
this.accessToken,
|
||||
this.expiresIn,
|
||||
});
|
||||
|
||||
factory Turn14StatusModel.fromJson(Map<String, dynamic> json) {
|
||||
return Turn14StatusModel(
|
||||
userId: json["userid"],
|
||||
hasCredentials: json["hasCredentials"] ?? false,
|
||||
clientId: json["credentials"]?["turn14clientid"],
|
||||
clientSecret: json["credentials"]?["turn14clientsecret"],
|
||||
accessToken: json["tokenInfo"]?["access_token"],
|
||||
expiresIn: json["tokenInfo"]?["expires_in"],
|
||||
);
|
||||
}
|
||||
|
||||
Turn14StatusEntity toEntity() {
|
||||
return Turn14StatusEntity(
|
||||
userId: userId,
|
||||
hasCredentials: hasCredentials,
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
accessToken: accessToken,
|
||||
expiresIn: expiresIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
77
lib/data/repositories/turn14_repository_impl.dart
Normal file
77
lib/data/repositories/turn14_repository_impl.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:autos/core/constants/api_endpoints.dart';
|
||||
import 'package:autos/data/models/turn14_model.dart';
|
||||
import 'package:autos/data/sources/remote/api_service.dart';
|
||||
import 'package:autos/domain/entities/turn14.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
abstract class Turn14Repository {
|
||||
/// Save Turn14 credentials
|
||||
Future<Turn14Entity> save({
|
||||
required String userId,
|
||||
required String clientId,
|
||||
required String clientSecret,
|
||||
});
|
||||
}
|
||||
|
||||
class Turn14RepositoryImpl implements Turn14Repository {
|
||||
final ApiService _apiService;
|
||||
|
||||
Turn14RepositoryImpl(this._apiService);
|
||||
|
||||
@override
|
||||
Future<Turn14Entity> save({
|
||||
required String userId,
|
||||
required String clientId,
|
||||
required String clientSecret,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _apiService.post(ApiEndpoints.turn14Save, {
|
||||
"userid": userId,
|
||||
"turn14clientid": clientId,
|
||||
"turn14clientsecret": clientSecret,
|
||||
});
|
||||
|
||||
// Check status
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
|
||||
if (data['code'] == "TURN14_SAVED") {
|
||||
/// Convert Response → Entity
|
||||
return Turn14Response.fromJson(data).toEntity();
|
||||
} else {
|
||||
throw Exception(
|
||||
data['message'] ?? "Failed to save Turn14 credentials",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw Exception("Server error: ${response.statusCode}");
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw Exception("Network error: ${e.message}");
|
||||
} catch (e) {
|
||||
throw Exception("Unexpected error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<Turn14StatusEntity> status(String userId) async {
|
||||
try {
|
||||
final response = await _apiService.post(ApiEndpoints.turn14Status, {
|
||||
"userid": userId,
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
|
||||
if (data["code"] == "TURN14_STATUS") {
|
||||
return Turn14StatusModel.fromJson(data).toEntity();
|
||||
} else {
|
||||
throw Exception(data["message"] ?? "Failed to fetch Turn14 status");
|
||||
}
|
||||
} else {
|
||||
throw Exception("Server error: ${response.statusCode}");
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception("Network/Unexpected error: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
31
lib/domain/entities/turn14.dart
Normal file
31
lib/domain/entities/turn14.dart
Normal file
@ -0,0 +1,31 @@
|
||||
class Turn14Entity {
|
||||
final String code;
|
||||
final String message;
|
||||
final String userId;
|
||||
final String accessToken;
|
||||
|
||||
const Turn14Entity({
|
||||
required this.code,
|
||||
required this.message,
|
||||
required this.userId,
|
||||
required this.accessToken,
|
||||
});
|
||||
}
|
||||
|
||||
class Turn14StatusEntity {
|
||||
final String userId;
|
||||
final bool hasCredentials;
|
||||
final String? clientId;
|
||||
final String? clientSecret;
|
||||
final String? accessToken;
|
||||
final String? expiresIn;
|
||||
|
||||
Turn14StatusEntity({
|
||||
required this.userId,
|
||||
required this.hasCredentials,
|
||||
this.clientId,
|
||||
this.clientSecret,
|
||||
this.accessToken,
|
||||
this.expiresIn,
|
||||
});
|
||||
}
|
||||
91
lib/presentation/providers/turn14_provider.dart
Normal file
91
lib/presentation/providers/turn14_provider.dart
Normal file
@ -0,0 +1,91 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:autos/data/models/turn14_model.dart';
|
||||
import 'package:autos/data/repositories/turn14_repository_impl.dart';
|
||||
import 'package:autos/data/sources/remote/api_service.dart';
|
||||
import 'package:autos/domain/entities/turn14.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
|
||||
/// ------------------------------------------------------------
|
||||
/// Service Providers
|
||||
/// ------------------------------------------------------------
|
||||
|
||||
final turn14ApiServiceProvider =
|
||||
Provider<ApiService>((ref) => ApiService());
|
||||
|
||||
final turn14RepositoryProvider = Provider<Turn14RepositoryImpl>((ref) {
|
||||
return Turn14RepositoryImpl(ref.read(turn14ApiServiceProvider));
|
||||
});
|
||||
|
||||
|
||||
/// ------------------------------------------------------------
|
||||
/// Turn14 Notifier
|
||||
/// ------------------------------------------------------------
|
||||
|
||||
class Turn14Notifier extends StateNotifier<AsyncValue<Turn14Entity?>> {
|
||||
final Turn14RepositoryImpl repository;
|
||||
final _storage = const FlutterSecureStorage();
|
||||
|
||||
static const _turn14StorageKey = "turn14_credentials";
|
||||
|
||||
Turn14Notifier(this.repository) : super(const AsyncValue.data(null));
|
||||
|
||||
/// Save Turn14 credentials
|
||||
Future<void> saveCredentials({
|
||||
required String userId,
|
||||
required String clientId,
|
||||
required String clientSecret,
|
||||
}) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final response = await repository.save(
|
||||
userId: userId,
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
);
|
||||
|
||||
// if (response is Turn14Response) {
|
||||
// await _storage.write(
|
||||
// key: _turn14StorageKey,
|
||||
// value: response.toRawJson(),
|
||||
// );
|
||||
// }
|
||||
|
||||
state = AsyncValue.data(response);
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load saved Turn14 credentials
|
||||
Future<void> loadSavedCredentials() async {
|
||||
final saved = await _storage.read(key: _turn14StorageKey);
|
||||
if (saved == null) return;
|
||||
|
||||
final decoded = jsonDecode(saved);
|
||||
final model = Turn14Response.fromJson(decoded);
|
||||
|
||||
state = AsyncValue.data(model as Turn14Entity?);
|
||||
}
|
||||
|
||||
/// Clear saved Turn14 data
|
||||
Future<void> clear() async {
|
||||
await _storage.delete(key: _turn14StorageKey);
|
||||
state = const AsyncValue.data(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// ------------------------------------------------------------
|
||||
/// Riverpod Provider
|
||||
/// ------------------------------------------------------------
|
||||
|
||||
final turn14Provider =
|
||||
StateNotifierProvider<Turn14Notifier, AsyncValue<Turn14Entity?>>((ref) {
|
||||
final repository = ref.read(turn14RepositoryProvider);
|
||||
return Turn14Notifier(repository);
|
||||
});
|
||||
124
lib/presentation/screens/ebay/ebay_screen.dart
Normal file
124
lib/presentation/screens/ebay/ebay_screen.dart
Normal file
@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
|
||||
class EbayScreen extends StatefulWidget {
|
||||
const EbayScreen({super.key});
|
||||
|
||||
@override
|
||||
State<EbayScreen> createState() => _EbayScreenState();
|
||||
}
|
||||
|
||||
class _EbayScreenState extends State<EbayScreen> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String selected = "ebay";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: SideMenu(
|
||||
selected: selected,
|
||||
onItemSelected: (key) {
|
||||
setState(() => selected = key);
|
||||
},
|
||||
),
|
||||
|
||||
// backgroundColor: const Color(0xFFEFFAFF),
|
||||
body: Stack(
|
||||
children: [
|
||||
/// TITLE
|
||||
Positioned(
|
||||
top: topPadding,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"eBay Settings",
|
||||
style: const TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// MAIN BOX UI
|
||||
Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.90,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
|
||||
margin: EdgeInsets.only(top: topPadding + 60),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
blurRadius: 25,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
/// Description
|
||||
Text(
|
||||
"Connect your eBay store to enable product sync, inventory updates, and order flow.",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.black54,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
/// BUTTON
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// TODO: Add eBay authorization flow
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00CFFF),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Connect your eBay store",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text(
|
||||
"You'll be redirected to eBay to authorize access, then returned here.",
|
||||
style: const TextStyle(fontSize: 13, color: Colors.black45),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// HAMBURGER BUTTON
|
||||
HamburgerButton(scaffoldKey: _scaffoldKey),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
215
lib/presentation/screens/store/create_location_screen.dart
Normal file
215
lib/presentation/screens/store/create_location_screen.dart
Normal file
@ -0,0 +1,215 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CreateLocationScreen extends StatefulWidget {
|
||||
const CreateLocationScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CreateLocationScreen> createState() => _CreateLocationScreenState();
|
||||
}
|
||||
|
||||
class _CreateLocationScreenState extends State<CreateLocationScreen> {
|
||||
// Controllers
|
||||
final TextEditingController storeName = TextEditingController();
|
||||
final TextEditingController phone = TextEditingController();
|
||||
final TextEditingController address1 = TextEditingController();
|
||||
final TextEditingController city = TextEditingController();
|
||||
final TextEditingController stateCtrl = TextEditingController();
|
||||
final TextEditingController postalCode = TextEditingController();
|
||||
final TextEditingController country = TextEditingController();
|
||||
final TextEditingController timeZone = TextEditingController(text: "America/New_York");
|
||||
TimeOfDay openTime = const TimeOfDay(hour: 9, minute: 0);
|
||||
TimeOfDay closeTime = const TimeOfDay(hour: 18, minute: 0);
|
||||
|
||||
Future<void> pickTime({required bool isOpen}) async {
|
||||
final TimeOfDay? picked = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: isOpen ? openTime : closeTime,
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
if (isOpen) openTime = picked;
|
||||
else closeTime = picked;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFEFFAFF),
|
||||
appBar: AppBar(
|
||||
title: const Text("Create New Location"),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_input("Store Name *", "Enter store name", storeName),
|
||||
_input("Phone *", "Enter phone number", phone),
|
||||
_input("Address Line 1 *", "Enter address", address1),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _input("City *", "City", city)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _input("State *", "State", stateCtrl)),
|
||||
],
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _input("Postal Code *", "Postal Code", postalCode)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _input("Country *", "Country Code (e.g. US)", country)),
|
||||
],
|
||||
),
|
||||
|
||||
_input("Time Zone *", "America/New_York", timeZone),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _timePicker(
|
||||
label: "Open Time *",
|
||||
time: openTime,
|
||||
onTap: () => pickTime(isOpen: true),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _timePicker(
|
||||
label: "Close Time *",
|
||||
time: closeTime,
|
||||
onTap: () => pickTime(isOpen: false),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
final json = {
|
||||
"store_name": storeName.text,
|
||||
"phone": phone.text,
|
||||
"address1": address1.text,
|
||||
"city": city.text,
|
||||
"state": stateCtrl.text,
|
||||
"postal_code": postalCode.text,
|
||||
"country": country.text,
|
||||
"timezone": timeZone.text,
|
||||
"open_time": openTime.format(context),
|
||||
"close_time": closeTime.format(context),
|
||||
};
|
||||
|
||||
print("📦 LOCATION JSON → $json");
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00CFFF),
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Save & Console JSON",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable TextField
|
||||
Widget _input(String label, String hint, TextEditingController controller) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF0F6FF),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Time Picker Widget
|
||||
Widget _timePicker({
|
||||
required String label,
|
||||
required TimeOfDay time,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0F6FF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(time.format(context),
|
||||
style: const TextStyle(fontSize: 16)),
|
||||
const Spacer(),
|
||||
const Icon(Icons.access_time, size: 20),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
131
lib/presentation/screens/store/store.dart
Normal file
131
lib/presentation/screens/store/store.dart
Normal file
@ -0,0 +1,131 @@
|
||||
import 'package:autos/core/routing/route_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
|
||||
class StoreScreen extends StatefulWidget {
|
||||
const StoreScreen({super.key});
|
||||
|
||||
@override
|
||||
State<StoreScreen> createState() => _StoreScreenState();
|
||||
}
|
||||
|
||||
class _StoreScreenState extends State<StoreScreen> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String selected = "ebay";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: SideMenu(
|
||||
selected: selected,
|
||||
onItemSelected: (key) {
|
||||
setState(() => selected = key);
|
||||
},
|
||||
),
|
||||
|
||||
body: Stack(
|
||||
children: [
|
||||
/// TITLE
|
||||
Positioned(
|
||||
top: topPadding,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
"eBay Locations",
|
||||
style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// MAIN WITH BUTTONS
|
||||
Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.90,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 40),
|
||||
margin: EdgeInsets.only(top: topPadding + 60),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
blurRadius: 25,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
/// SAVE SELECTED BUTTON
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// TODO: Save Selected Flow
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00CFFF),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Save Selected",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
/// CREATE NEW LOCATION BUTTON
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutePaths.createStoreLocation,
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00CFFF),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Create New Location",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// HAMBURGER BUTTON
|
||||
HamburgerButton(scaffoldKey: _scaffoldKey),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,10 @@ import 'package:autos/core/theme/app_typography.dart';
|
||||
import 'package:autos/core/widgets/hamburger_button.dart';
|
||||
import 'package:autos/core/widgets/side_menu.dart';
|
||||
import 'package:autos/presentation/providers/user_provider.dart';
|
||||
import 'package:autos/presentation/providers/turn14_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
class Turn14Screen extends ConsumerStatefulWidget {
|
||||
const Turn14Screen({super.key});
|
||||
@ -16,9 +18,16 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String selected = 'turn14';
|
||||
|
||||
// controllers
|
||||
final TextEditingController clientIdController = TextEditingController();
|
||||
final TextEditingController clientSecretController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(userDetailsProvider);
|
||||
final asyncUser = ref.watch(userDetailsProvider);
|
||||
final turn14State = ref.watch(turn14Provider);
|
||||
|
||||
final user = asyncUser.value;
|
||||
final double topPadding = MediaQuery.of(context).padding.top + 16;
|
||||
|
||||
return Scaffold(
|
||||
@ -46,13 +55,13 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
),
|
||||
),
|
||||
|
||||
/// Main Scrollable UI
|
||||
SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Description Row
|
||||
Row(
|
||||
children: [
|
||||
Text("⚡", style: const TextStyle(fontSize: 28)),
|
||||
@ -70,21 +79,16 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Client ID Input
|
||||
_inputField(
|
||||
label: "Client ID",
|
||||
controller: TextEditingController(),
|
||||
),
|
||||
_inputField(label: "Client ID", controller: clientIdController),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Secret Key Input With Eye Icon
|
||||
_passwordField(
|
||||
label: "Secret Key",
|
||||
controller: TextEditingController(),
|
||||
controller: clientSecretController,
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
// Save Button
|
||||
/// SAVE BUTTON
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@ -95,65 +99,70 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
),
|
||||
backgroundColor: const Color(0xFF00C9FF),
|
||||
),
|
||||
onPressed: () {},
|
||||
child: const Text(
|
||||
"Save Credentials",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
onPressed: turn14State.isLoading
|
||||
? null
|
||||
: () async {
|
||||
if (user == null) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "⚠️ User not found",
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientIdController.text.trim().isEmpty ||
|
||||
clientSecretController.text.trim().isEmpty) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "⚠️ Please fill all fields",
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.orange,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(turn14Provider.notifier)
|
||||
.saveCredentials(
|
||||
userId: user.id,
|
||||
clientId: clientIdController.text.trim(),
|
||||
clientSecret:
|
||||
clientSecretController.text.trim(),
|
||||
);
|
||||
|
||||
Fluttertoast.showToast(
|
||||
msg: "✅ Turn14 Credentials Saved Successfully",
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
},
|
||||
child: turn14State.isLoading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text(
|
||||
"Save Credentials",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Info Box
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8F1FF),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.info, color: Colors.blue),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
"No credentials saved yet.",
|
||||
style: TextStyle(color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_infoBox(),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Tips Box
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8FCFF),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text(
|
||||
"💡 Connection Tips",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text("• Ensure your credentials are valid and active."),
|
||||
Text("• Credentials are encrypted before saving."),
|
||||
Text("• Contact Turn14 support for API setup help."),
|
||||
],
|
||||
),
|
||||
),
|
||||
_tipsBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -164,6 +173,7 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// UI COMPONENTS
|
||||
Widget _inputField({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
@ -224,4 +234,49 @@ class _Turn14ScreenState extends ConsumerState<Turn14Screen> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoBox() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8F1FF),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.info, color: Colors.blue),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
"No credentials saved yet.",
|
||||
style: TextStyle(color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tipsBox() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8FCFF),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text(
|
||||
"💡 Connection Tips",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text("• Ensure your credentials are valid and active."),
|
||||
Text("• Credentials are encrypted before saving."),
|
||||
Text("• Contact Turn14 support for API setup help."),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
72
pubspec.lock
72
pubspec.lock
@ -17,6 +17,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.7.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.7"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -49,6 +57,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -57,6 +73,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -214,6 +238,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -328,6 +360,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -344,6 +384,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -488,6 +536,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -512,6 +568,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -733,6 +797,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
10
pubspec.yaml
10
pubspec.yaml
@ -20,6 +20,7 @@ dependencies:
|
||||
fluttertoast: ^9.0.0
|
||||
flutter_secure_storage: ^9.2.4
|
||||
youtube_player_flutter: ^9.1.3
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -48,4 +49,11 @@ flutter:
|
||||
- asset: assets/fonts/Nunito-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/Nunito-Bold.ttf
|
||||
weight: 700
|
||||
weight: 700
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "assets/auth/autos_transp.png"
|
||||
adaptive_icon_background: "#FFFFFF"
|
||||
adaptive_icon_foreground: "assets/auth/autos_transp.png"
|
||||
Loading…
x
Reference in New Issue
Block a user