Compare commits

...

13 Commits

Author SHA1 Message Date
f182c80210 fix: price reduction on deletion 2024-06-25 22:51:14 +03:00
cefabd1c70 fix: img loading on changing search text 2024-06-25 22:36:18 +03:00
f1055d40a4 fix: remove spacer 2024-06-25 13:58:41 +03:00
cfe0184e3a fix: card width 2024-06-24 22:35:47 +03:00
faa52dcaa2 fix: interface fixes 2024-06-24 14:56:06 +03:00
946d2ada41 fix: some fixes in example app 2024-06-24 14:08:02 +03:00
7cb92a7b83 fix: delete toLocal duplicate 2024-06-19 15:38:52 +03:00
dd59a605ad fix: some 2024-06-18 21:19:23 +03:00
34c0ea5fa1 feat: clear btn in web 2024-06-18 20:47:54 +03:00
893b925a04 fix: ultra-fake orders 2024-06-18 20:40:45 +03:00
bdcd4507c2 feat: fake orders 2024-06-18 15:14:37 +03:00
fb5538ab79 fix: order info page 2024-06-17 16:59:10 +03:00
0a22b5c051 Feat: order info page 2024-06-16 23:33:38 +03:00
17 changed files with 975 additions and 216 deletions

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gymlink_module_web/pages/order_info.dart';
import 'package:gymlink_module_web/tools/routes.dart';
enum OrderStatus { created, inProgress, completed, canceled } enum OrderStatus { created, inProgress, completed, canceled }
@@ -14,15 +16,13 @@ class HistoryItemCard extends StatelessWidget {
final String id; final String id;
final String cost; final String cost;
final String date; final String date;
final Image image; final Widget image;
final OrderStatus status;
const HistoryItemCard({ const HistoryItemCard({
super.key, super.key,
required this.id, required this.id,
required this.cost, required this.cost,
required this.date, required this.date,
required this.status,
required this.image, required this.image,
}); });
@@ -38,6 +38,11 @@ class HistoryItemCard extends StatelessWidget {
minWidth: 600, minWidth: 600,
maxWidth: 800, maxWidth: 800,
), ),
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
CustomPageRoute(builder: (context) => OrderInfoPage(id: id)));
},
child: Card( child: Card(
elevation: 4, elevation: 4,
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
@@ -62,8 +67,6 @@ class HistoryItemCard extends StatelessWidget {
MarkdownBody( MarkdownBody(
data: '### Заказ **№$id** от $date', data: '### Заказ **№$id** от $date',
), ),
MarkdownBody(
data: 'Статус: **${orderStatusMap[status]}**'),
MarkdownBody(data: 'Сумма: **$cost руб.**'), MarkdownBody(data: 'Сумма: **$cost руб.**'),
], ],
) )
@@ -74,6 +77,7 @@ class HistoryItemCard extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@@ -29,7 +29,8 @@ class ProductCard extends StatelessWidget {
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: 160, minHeight: 160,
maxHeight: getCardHeight(context: context), maxHeight: getCardHeight(context: context),
), minWidth: 180,
maxWidth: 250),
child: Card( child: Card(
elevation: 3, elevation: 3,
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class OrderDetailCardItemCard extends StatelessWidget {
final String name;
final int count;
final double price;
final Widget image;
const OrderDetailCardItemCard(
{super.key,
required this.image,
required this.name,
required this.count,
required this.price});
@override
Widget build(BuildContext context) {
return Padding(
padding:
const EdgeInsetsDirectional.symmetric(horizontal: 10, vertical: 10),
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 130),
child: Card(
elevation: 4,
color: Theme.of(context).scaffoldBackgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
image,
const SizedBox(width: 20),
Column(
mainAxisSize: MainAxisSize.min,
children: [
MarkdownBody(data: '# $name'),
Text(
'${price.toStringAsFixed(2)} руб. x $count = ${(price * count).toStringAsFixed(2)} руб.'),
],
)
],
),
MarkdownBody(data: '# X$count')
],
),
),
),
),
);
}
}

View File

@@ -31,6 +31,8 @@ class GymItem {
final double price; final double price;
final String categoryId; final String categoryId;
final List<GymImage> images; final List<GymImage> images;
final String supplierId;
final String supplierName;
int localCount = 0; int localCount = 0;
GymItem({ GymItem({
@@ -42,13 +44,16 @@ class GymItem {
required this.price, required this.price,
required this.categoryId, required this.categoryId,
required this.images, required this.images,
required this.supplierId,
required this.supplierName,
}); });
factory GymItem.fromRawJson(String str) => GymItem.fromJson(json.decode(str)); factory GymItem.fromRawJson(String str) => GymItem.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson()); String toRawJson() => json.encode(toJson());
factory GymItem.fromJson(Map<String, dynamic> json) => GymItem( factory GymItem.fromJson(Map<String, dynamic> json) {
return GymItem(
id: json["id"], id: json["id"],
externalId: json["ExternalId"], externalId: json["ExternalId"],
title: json["title"], title: json["title"],
@@ -56,9 +61,14 @@ class GymItem {
count: json["count"], count: json["count"],
price: json["price"] / 100, price: json["price"] / 100,
categoryId: json["categoryId"], categoryId: json["categoryId"],
images: List<GymImage>.from( images:
json["images"].map((x) => GymImage.fromJson(x))), List<GymImage>.from(json["images"].map((x) => GymImage.fromJson(x))),
supplierId: json["supplier"] == null ? '' : json["supplier"]["id"] ?? '',
supplierName: json["supplier"] == null
? ''
: json["supplier"]["title"] ?? "Поставщик",
); );
}
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"id": id, "id": id,
@@ -69,6 +79,8 @@ class GymItem {
"price": price, "price": price,
"categoryId": categoryId, "categoryId": categoryId,
"images": List<dynamic>.from(images.map((x) => x.toJson())), "images": List<dynamic>.from(images.map((x) => x.toJson())),
"supplier":
supplierId == '' ? null : {"id": supplierId, "title": supplierName},
}; };
} }
@@ -148,3 +160,166 @@ class GymCategory {
"name": name, "name": name,
}; };
} }
class GymHistoryItem {
String id;
final String date;
final String sum;
final String photo;
final String timestamp;
GymHistoryItem(
{required this.id,
required this.date,
required this.sum,
required this.photo,
required this.timestamp});
factory GymHistoryItem.fromRawJson(String str) =>
GymHistoryItem.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GymHistoryItem.fromJson(Map<String, dynamic> json) => GymHistoryItem(
id: json["id"],
date: json["date"],
sum: json["sum"],
photo: json["photo"],
timestamp: json["timestamp"],
);
Map<String, dynamic> toJson() => {
"id": id,
"date": date,
"sum": sum,
"timestamp": timestamp,
"photo": photo,
};
}
class GymHistoryItemDetail {
String id;
final String date;
final String sum;
String? payUrl;
final String receiver;
final String email;
final String timestamp;
final String address;
final List<GymHistoryItemDetailProvider> providers;
GymHistoryItemDetail(
{required this.id,
required this.date,
required this.sum,
this.payUrl,
required this.providers,
required this.receiver,
required this.email,
required this.address,
required this.timestamp});
factory GymHistoryItemDetail.fromRawJson(String str) =>
GymHistoryItemDetail.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GymHistoryItemDetail.fromJson(Map<String, dynamic> json) =>
GymHistoryItemDetail(
id: json["id"],
date: json["date"],
sum: json["sum"],
receiver: json["receiver"],
email: json["email"],
address: json["address"],
payUrl: json["pay_url"] as String?,
providers: List<GymHistoryItemDetailProvider>.from(json["providers"]
.map((x) => GymHistoryItemDetailProvider.fromJson(x))),
timestamp: json["timestamp"]);
Map<String, dynamic> toJson() => {
"id": id,
"date": date,
"sum": sum,
"pay_url": payUrl,
"providers": List<dynamic>.from(providers.map((x) => x.toJson())),
"receiver": receiver,
"email": email,
"timestamp": timestamp,
"address": address,
};
}
class GymHistoryItemDetailProvider {
final String id;
final String name;
String status;
final List<GymHistoryItemDetailItem> items;
GymHistoryItemDetailProvider({
required this.id,
required this.name,
required this.status,
required this.items,
});
factory GymHistoryItemDetailProvider.fromRawJson(String str) =>
GymHistoryItemDetailProvider.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GymHistoryItemDetailProvider.fromJson(Map<String, dynamic> json) {
return GymHistoryItemDetailProvider(
id: json["id"],
name: json["name"],
status: json["status"],
items: List<GymHistoryItemDetailItem>.from(
json["items"].map((x) => GymHistoryItemDetailItem.fromJson(x))),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"status": status,
"items": List<dynamic>.from(items.map((x) => x.toJson())),
};
}
class GymHistoryItemDetailItem {
final String photo;
final String id;
final int count;
final String name;
final String price;
GymHistoryItemDetailItem({
required this.photo,
required this.id,
required this.count,
required this.price,
required this.name,
});
factory GymHistoryItemDetailItem.fromRawJson(String str) =>
GymHistoryItemDetailItem.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory GymHistoryItemDetailItem.fromJson(Map<String, dynamic> json) =>
GymHistoryItemDetailItem(
photo: json["photo"],
id: json["id"],
count: json["count"],
price: json["price"],
name: json["name"],
);
Map<String, dynamic> toJson() => {
"photo": photo,
"id": id,
"count": count,
"price": price,
"name": name,
};
}

View File

@@ -2,10 +2,12 @@ import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gymlink_module_web/main_mobile.dart'; import 'package:gymlink_module_web/main_mobile.dart';
import 'package:gymlink_module_web/providers/main.dart'; import 'package:gymlink_module_web/providers/main.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() { void main() {
runApp(const MyExampleApp()); runApp(const MyExampleApp());
@@ -31,6 +33,8 @@ class MyExampleApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
return MaterialApp( return MaterialApp(
title: 'GymLink Example App', title: 'GymLink Example App',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
@@ -54,16 +58,32 @@ Widget getDrawer(BuildContext context) => Drawer(
ListTile( ListTile(
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
title: const Text('Home'), title: const Text('Home'),
onTap: () => Navigator.of(context).push( onTap: () {
Future.microtask(() async {
final prefs = await SharedPreferences.getInstance();
prefs.remove('token');
prefs.remove('history');
prefs.remove('cart');
prefs.remove('detail_history');
});
Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const ExampleMainPage(), builder: (context) => const ExampleMainPage(),
), ),
), );
), }),
ListTile( ListTile(
leading: const Icon(Icons.sell), leading: const Icon(Icons.sell),
title: const Text('Club 2'), title: const Text('Club 2'),
onTap: () => Navigator.of(context).push( onTap: () {
Future.microtask(() async {
final prefs = await SharedPreferences.getInstance();
prefs.remove('token');
prefs.remove('history');
prefs.remove('cart');
prefs.remove('detail_history');
});
Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChangeNotifierProvider( builder: (context) => ChangeNotifierProvider(
create: (_) => GymLinkProvider(), create: (_) => GymLinkProvider(),
@@ -72,12 +92,13 @@ Widget getDrawer(BuildContext context) => Drawer(
), ),
), ),
), ),
), );
), }),
ListTile( ListTile(
leading: const Icon(Icons.search), leading: const Icon(Icons.search),
title: const Text('Example page'), title: const Text('Example page'),
onTap: () => Navigator.of(context).push(MaterialPageRoute( onTap: () =>
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const ExampleSecondPage(), builder: (context) => const ExampleSecondPage(),
)), )),
), ),
@@ -142,26 +163,25 @@ class _ExamplePageState extends State<ExamplePage> {
), ),
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
drawer: getDrawer(context), drawer: getDrawer(context),
body: const Column( body: Column(
children: [ children: [
Text('test'), IconButton(
Expanded( icon: const Icon(Icons.colorize),
child: MyApp(),
),
SizedBox(
height: 20,
),
Text('Bottom text')
],
),
floatingActionButton: IconButton(
icon: const Icon(Icons.search),
onPressed: () { onPressed: () {
context.read<GymLinkProvider>().changeTheme( context.read<GymLinkProvider>().changeTheme(
Random().nextInt(0xffffff + 1), Random().nextInt(0xffffff + 1),
blackTheme: Random().nextBool()); blackTheme: Random().nextBool());
}, },
), ),
const Expanded(
child: MyApp(),
),
const SizedBox(
height: 20,
),
const Text('Bottom text')
],
),
); );
} }
} }
@@ -230,12 +250,6 @@ class _ExampleClub2PageState extends State<ExampleClub2Page> {
Text('Bottom text') Text('Bottom text')
], ],
), ),
floatingActionButton: IconButton(
icon: const Icon(Icons.search),
onPressed: () {
// context.read<GymLinkProvider>().changeTheme(0xFFAABCAB);
},
),
); );
} }
} }
@@ -250,8 +264,16 @@ class ExampleSecondPage extends StatelessWidget {
title: const Text('GymLink Example App'), title: const Text('GymLink Example App'),
), ),
drawer: getDrawer(context), drawer: getDrawer(context),
body: const Center( body: Center(
child: Text('Example page'), child: TextButton(
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
prefs.remove('token');
prefs.remove('history');
prefs.remove('cart');
prefs.remove('detail_history');
},
child: const Text('Clear')),
), ),
); );
} }

View File

@@ -155,6 +155,8 @@ class _BasketPageState extends State<BasketPage> {
removeItemFromCart(id); removeItemFromCart(id);
setState(() { setState(() {
cartItems.removeWhere((element) => element.id == id); cartItems.removeWhere((element) => element.id == id);
totalPrice = cartItems.fold(
0, (sum, item) => sum + item.price * item.localCount);
}); });
if (mounted) { if (mounted) {
_updateCart(); _updateCart();
@@ -311,7 +313,7 @@ class _BasketPageState extends State<BasketPage> {
), ),
), ),
), ),
_buildSpacer(), // _buildSpacer(),
Padding( Padding(
padding: const EdgeInsetsDirectional.symmetric( padding: const EdgeInsetsDirectional.symmetric(
horizontal: 10, vertical: 10), horizontal: 10, vertical: 10),
@@ -354,7 +356,7 @@ class _BasketPageState extends State<BasketPage> {
], ],
), ),
), ),
const SizedBox(width: 50), // const SizedBox(width: 50),
], ],
), ),
), ),

View File

@@ -88,12 +88,6 @@ class _DetailPageState extends State<DetailPage> {
required BuildContext context, required BuildContext context,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceAround, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceAround,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center}) { CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center}) {
// if (false && MediaQuery.of(context).size.width > 600) {
// return Row(
// mainAxisAlignment: mainAxisAlignment,
// crossAxisAlignment: crossAxisAlignment,
// children: children);
// }
return Column( return Column(
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment, crossAxisAlignment: crossAxisAlignment,
@@ -202,7 +196,6 @@ class _DetailPageState extends State<DetailPage> {
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: SizedBox( child: SizedBox(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
// height: MediaQuery.sizeOf(context).height,
child: _buildRowOrCol( child: _buildRowOrCol(
context: context, context: context,
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@@ -288,12 +281,14 @@ class _DetailPageState extends State<DetailPage> {
: categoryName!) : categoryName!)
: ''), : ''),
backgroundColor: Colors.white, backgroundColor: Colors.white,
labelStyle:
const TextStyle(color: Colors.black),
), ),
), ),
), ),
Center( Center(
child: MarkdownBody( child: MarkdownBody(
data: '### Отстаток: _${item!.count}_', data: '### Остаток: _${item!.count}_',
)), )),
item!.description != '' item!.description != ''
? Padding( ? Padding(

View File

@@ -10,7 +10,6 @@ import 'package:gymlink_module_web/pages/order_history.dart';
import 'package:gymlink_module_web/providers/cart.dart'; import 'package:gymlink_module_web/providers/cart.dart';
import 'package:gymlink_module_web/tools/items.dart'; import 'package:gymlink_module_web/tools/items.dart';
import 'package:gymlink_module_web/tools/prefs.dart'; import 'package:gymlink_module_web/tools/prefs.dart';
import 'package:gymlink_module_web/tools/relative.dart';
import 'package:gymlink_module_web/tools/routes.dart'; import 'package:gymlink_module_web/tools/routes.dart';
import 'package:gymlink_module_web/tools/text.dart'; import 'package:gymlink_module_web/tools/text.dart';
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
@@ -74,6 +73,7 @@ class _MainPageState extends State<MainPage> {
List<GymCategory> categories = []; List<GymCategory> categories = [];
GymCategory? selectedCategory; GymCategory? selectedCategory;
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final TextEditingController _searchField = TextEditingController();
@override @override
void initState() { void initState() {
@@ -124,6 +124,9 @@ class _MainPageState extends State<MainPage> {
void _onSearch() { void _onSearch() {
final categoryId = selectedCategory == null ? '' : selectedCategory!.id; final categoryId = selectedCategory == null ? '' : selectedCategory!.id;
setState(() {
searchText = _searchField.text.trim().toLowerCase();
});
_searchItems(searchText: searchText, categoryId: categoryId); _searchItems(searchText: searchText, categoryId: categoryId);
} }
@@ -142,12 +145,13 @@ class _MainPageState extends State<MainPage> {
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: (value) => setState(() { onChanged: (value) {
searchText = value.trim().toLowerCase(); searchText = value.trim().toLowerCase();
if (searchText == '') { if (searchText == '') {
_onSearch(); _onSearch();
} }
}), },
controller: _searchField,
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onSubmitted: (_) => _onSearch(), onSubmitted: (_) => _onSearch(),
decoration: InputDecoration( decoration: InputDecoration(
@@ -179,7 +183,7 @@ class _MainPageState extends State<MainPage> {
), ),
), ),
), ),
getSpacer(context: context, flex: 2), // getSpacer(context: context, flex: 2),
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
@@ -270,12 +274,12 @@ class _MainPageState extends State<MainPage> {
SliverGridDelegateWithFixedCrossAxisCount( SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: min( crossAxisCount: min(
(MediaQuery.sizeOf(context).width ~/ (MediaQuery.sizeOf(context).width ~/
200) 220)
.toInt(), .toInt(),
8), 8),
childAspectRatio: 0.8, childAspectRatio: 0.8,
mainAxisSpacing: 10.0, mainAxisSpacing: 10.0,
crossAxisSpacing: 40.0), crossAxisSpacing: 20.0),
itemCount: itemViewCount, itemCount: itemViewCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final product = filteredData[index]; final product = filteredData[index];

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/components/app_bar.dart';
@@ -6,6 +8,7 @@ import 'package:gymlink_module_web/components/order_confirm_item_card.dart';
import 'package:gymlink_module_web/interfaces/items.dart'; import 'package:gymlink_module_web/interfaces/items.dart';
import 'package:gymlink_module_web/pages/order_history.dart'; import 'package:gymlink_module_web/pages/order_history.dart';
import 'package:gymlink_module_web/providers/cart.dart'; import 'package:gymlink_module_web/providers/cart.dart';
import 'package:gymlink_module_web/tools/history.dart';
import 'package:gymlink_module_web/tools/items.dart'; import 'package:gymlink_module_web/tools/items.dart';
import 'package:gymlink_module_web/tools/prefs.dart'; import 'package:gymlink_module_web/tools/prefs.dart';
import 'package:gymlink_module_web/tools/routes.dart'; import 'package:gymlink_module_web/tools/routes.dart';
@@ -64,6 +67,61 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
List<GymItem> gymCart = []; List<GymItem> gymCart = [];
bool isAgree = false; bool isAgree = false;
bool _isLoading = true; bool _isLoading = true;
final _emailController = TextEditingController();
final _addressController = TextEditingController();
final _nameController = TextEditingController();
Future<void> _addOrderToHistory() async {
String name = _nameController.text;
String email = _emailController.text;
String address = _addressController.text;
Set<String> supplierIdsSet = {};
for (final item in cartItems) {
supplierIdsSet.add(item.supplierId);
}
List<GymHistoryItemDetailProvider> providers = [];
for (final supplierId in supplierIdsSet) {
List<GymItem> items =
cartItems.where((e) => e.supplierId == supplierId).toList();
List<GymHistoryItemDetailItem> detailItems = [];
for (final item in items) {
detailItems.add(GymHistoryItemDetailItem(
id: item.id,
photo: item.images[0].url,
count: item.localCount,
price: item.price.toString(),
name: item.title,
));
}
GymHistoryItemDetailProvider provider = GymHistoryItemDetailProvider(
id: supplierId,
name: items.first.supplierName,
items: detailItems,
// status: 'Не оплачен'
status: Random().nextBool()
? 'Не оплачен'
: Random().nextBool()
? 'Не оплачен'
: Random().nextBool()
? 'Сборка'
: 'Доставляется',
);
providers.add(provider);
}
final order = GymHistoryItemDetail(
id: Random().nextInt(1000000).toString(),
receiver: name,
email: email,
address: address,
sum: totalPrice.toString(),
date: '',
providers: providers,
timestamp: DateTime.now().millisecondsSinceEpoch.toString(),
);
await addToHistory(order);
}
@override @override
void initState() { void initState() {
@@ -93,6 +151,65 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
} }
} }
bool _checkInputs() {
final email = _emailController.text;
final address = _addressController.text;
final name = _nameController.text;
if (!RegExp(r"^((?!\.)[\w\-_.]*[^.])(@\w+)(\.\w+(\.\w+)?[^.\W])$")
.hasMatch(email)) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Некорректный email'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('ОК'),
)
],
),
);
return false;
}
if (address.isEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Адрес не может быть пустым'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('ОК'),
)
],
),
);
return false;
}
if (name.isEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('ФИО не может быть пустым'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('ОК'),
)
],
),
);
return false;
}
return true;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -148,6 +265,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
data: '## Итого: ${totalPrice.toStringAsFixed(2)} руб.'), data: '## Итого: ${totalPrice.toStringAsFixed(2)} руб.'),
Expanded( Expanded(
child: TextField( child: TextField(
controller: _addressController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Адрес доставки', hintText: 'Адрес доставки',
border: OutlineInputBorder( border: OutlineInputBorder(
@@ -158,6 +276,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
), ),
Expanded( Expanded(
child: TextField( child: TextField(
controller: _emailController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Электронная почта', hintText: 'Электронная почта',
border: OutlineInputBorder( border: OutlineInputBorder(
@@ -169,6 +288,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
), ),
Expanded( Expanded(
child: TextField( child: TextField(
controller: _nameController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Получатель', hintText: 'Получатель',
border: OutlineInputBorder( border: OutlineInputBorder(
@@ -179,8 +299,10 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
), ),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
if (!_checkInputs()) return;
_goToPage(); _goToPage();
await clearCart(); await clearCart();
await _addOrderToHistory();
Provider.of<CartProvider>(context, listen: false) Provider.of<CartProvider>(context, listen: false)
.updateCartLength(); .updateCartLength();
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).pushAndRemoveUntil(

View File

@@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/components/app_bar.dart';
import 'package:gymlink_module_web/components/heading.dart'; import 'package:gymlink_module_web/components/heading.dart';
import 'package:gymlink_module_web/components/history_item_card.dart'; import 'package:gymlink_module_web/components/history_item_card.dart';
import 'package:gymlink_module_web/tools/relative.dart'; import 'package:gymlink_module_web/interfaces/items.dart';
import 'package:gymlink_module_web/tools/history.dart';
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
List<Map<String, String>> orders = [ List<Map<String, String>> orders = [
@@ -45,32 +47,24 @@ class HistoryPage extends StatefulWidget {
} }
class _HistoryPageState extends State<HistoryPage> { class _HistoryPageState extends State<HistoryPage> {
List<Map<String, String>> my_orders = []; List<GymHistoryItem> my_orders = [];
late Timer _updateTimer; late Timer _updateTimer;
bool _isLoading = true;
bool _isRefreshing = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
my_orders = orders;
ordersRefresh(); ordersRefresh();
} }
void ordersRefresh() { void ordersRefresh() {
_updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh); _updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh);
Future.microtask(() => _onRefresh(_updateTimer));
} }
void _onLoad() async { void _onLoad() async {
await Future.delayed(const Duration(milliseconds: 1000)); await Future.delayed(const Duration(milliseconds: 1000));
setState(() {
my_orders.add(
{
"image": "product.png",
"price": "120",
"id": "666666",
"date": "11.09.2001"
},
);
});
} }
@override @override
@@ -81,7 +75,12 @@ class _HistoryPageState extends State<HistoryPage> {
Future<void> _onRefresh(Timer timer) async { Future<void> _onRefresh(Timer timer) async {
await Future.delayed(const Duration(milliseconds: 1000)); await Future.delayed(const Duration(milliseconds: 1000));
debugPrint('refreshed'); var orders = await getHistory();
setState(() {
my_orders = orders;
_isLoading = false;
_isRefreshing = false;
});
} }
@override @override
@@ -95,7 +94,43 @@ class _HistoryPageState extends State<HistoryPage> {
title: 'История заказов', title: 'История заказов',
toMain: true, toMain: true,
), ),
Expanded( const SizedBox(height: 5),
kIsWeb && !_isLoading
? Center(
child: ElevatedButton(
onPressed: () {
setState(() => _isRefreshing = true);
_onRefresh(_updateTimer);
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(50)),
),
foregroundColor: Colors.white,
fixedSize: const Size(180, 40),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Обновить'),
Spacer(),
Icon(Icons.refresh),
Spacer()
],
),
),
)
: const SizedBox.shrink(),
const SizedBox(height: 5),
_isRefreshing
? const Center(child: CircularProgressIndicator())
: const SizedBox.shrink(),
const SizedBox(height: 5),
_isLoading
? const Expanded(
child: Center(child: CircularProgressIndicator()))
: Expanded(
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -105,21 +140,36 @@ class _HistoryPageState extends State<HistoryPage> {
child: RefreshIndicator( child: RefreshIndicator(
edgeOffset: 55, edgeOffset: 55,
onRefresh: () => _onRefresh(_updateTimer), onRefresh: () => _onRefresh(_updateTimer),
child: Stack( child: my_orders.isEmpty
? const Center(child: Text('Нет заказов'))
: Stack(
children: [ children: [
ListView.builder( ListView.builder(
itemCount: my_orders.length, itemCount: my_orders.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = my_orders[index]; final item = my_orders[index];
return HistoryItemCard( return HistoryItemCard(
id: item['id']!, id: item.id,
cost: item['price']!, cost: double.parse(item.sum)
date: item['date']!, .toStringAsFixed(2),
image: Image( date: item.date,
image: AssetImage('assets/${item['image']!}'), image: FutureBuilder(
future: precacheImage(
NetworkImage(item.photo),
context),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return Image(
image: NetworkImage(
item.photo),
width: 50, width: 50,
);
} else {
return const CircularProgressIndicator();
}
},
), ),
status: OrderStatus.completed,
); );
}, },
), ),
@@ -128,7 +178,9 @@ class _HistoryPageState extends State<HistoryPage> {
), ),
), ),
), ),
getSpacer(context: context) // my_orders.isEmpty
// ? const SizedBox.shrink()
// : getSpacer(context: context)
], ],
), ),
), ),

265
lib/pages/order_info.dart Normal file
View File

@@ -0,0 +1,265 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gymlink_module_web/components/app_bar.dart';
import 'package:gymlink_module_web/components/heading.dart';
import 'package:gymlink_module_web/components/order_detail_item_card.dart';
import 'package:gymlink_module_web/interfaces/items.dart';
import 'package:gymlink_module_web/tools/history.dart';
import 'package:gymlink_module_web/tools/text.dart';
import 'package:url_launcher/url_launcher.dart';
final GymHistoryItemDetail item = GymHistoryItemDetail.fromJson({
"id": "12345",
"date": "01.01.1970",
"sum": "45000",
"pay_url": "https://example.org",
"receiver": "Иванов Иван Иванович",
"email": "a@a.ru",
"address": "г. Москва, ул. Пушкина, д. 17",
"providers": [
{
"id": "123",
"name": "Поставщик 1",
"status": "Доставлен",
"items": [
{"photo": "url", "id": "123", "count": 2, "price": "15000"},
{"photo": "url", "id": "123", "count": 2, "price": "15000"}
]
},
{
"id": "123",
"name": "Поставщик 1",
"status": "Доставляется",
"items": [
{"photo": "url", "id": "123", "count": 2, "price": "15000"}
]
}
]
});
class OrderInfoPage extends StatefulWidget {
final String id;
const OrderInfoPage({super.key, required this.id});
@override
State<StatefulWidget> createState() => _OrderInfoPageState();
}
class _OrderInfoPageState extends State<OrderInfoPage> {
GymHistoryItemDetail? detail;
final _scrollController = ScrollController();
late Timer _updateTimer;
bool _isRefreshing = false;
@override
void initState() {
super.initState();
_updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh);
_onRefresh(_updateTimer);
}
@override
void dispose() {
_updateTimer.cancel();
super.dispose();
}
Future<void> _onRefresh(Timer timer) async {
return Future.delayed(const Duration(milliseconds: 1000), () async {
var orderInfo = await getHistoryDetail(widget.id);
setState(() {
detail = orderInfo;
_isRefreshing = false;
});
});
}
Future<void> _goToPage() async {
if (detail?.payUrl != null) {
final Uri url = Uri.parse(detail?.payUrl ?? 'https://example.org');
if (!await launchUrl(url, webOnlyWindowName: '_blank')) {
throw 'Could not launch $url';
}
_onRefresh(_updateTimer);
}
}
Widget _buildContent() {
return Column(
children: [
GymLinkHeader(title: "Заказ #${detail?.id} от ${detail?.date}"),
Expanded(
child: RefreshIndicator(
onRefresh: () => _onRefresh(_updateTimer),
edgeOffset: 55,
child: Scrollbar(
controller: _scrollController,
child: ListView(
controller: _scrollController,
children: [
const SizedBox(height: 10),
kIsWeb
? Center(
child: ElevatedButton(
onPressed: () {
setState(() {
_isRefreshing = true;
});
_onRefresh(_updateTimer);
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(50)),
),
foregroundColor: Colors.white,
fixedSize: const Size(180, 40),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Обновить'),
Spacer(),
Icon(Icons.refresh),
Spacer()
],
),
),
)
: const SizedBox.shrink(),
const SizedBox(height: 5),
_isRefreshing
? const Center(
child: CircularProgressIndicator(),
)
: const SizedBox.shrink(),
const SizedBox(height: 5),
ListView.builder(
itemCount: detail!.providers.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
final provider = detail!.providers[index];
return Padding(
padding: const EdgeInsetsDirectional.symmetric(
vertical: 10,
horizontal: 5,
),
child: Card(
elevation: 3,
child: Column(
children: [
MarkdownBody(data: '# ${provider.name}'),
MarkdownBody(
data: '## Статус: ${provider.status}'),
const MarkdownBody(data: '### Состав:'),
for (final item in provider.items)
OrderDetailCardItemCard(
image: FutureBuilder(
future: precacheImage(
NetworkImage(item.photo), context),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return Image(
image: NetworkImage(item.photo),
width: 50,
);
} else {
return const CircularProgressIndicator();
}
},
),
name: shortString(item.name),
count: item.count,
price: double.parse(item.price),
),
],
),
),
);
},
),
const SizedBox(
height: 10,
),
SizedBox(
height: 200,
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: 5,
),
child: Card(
elevation: 4,
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: 10, vertical: 20),
child: Column(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownBody(
data:
'## Итого: ${double.parse(detail!.sum).toStringAsFixed(2)} руб.'),
MarkdownBody(
data:
"### Адрес получателя: __${detail!.address}__"),
MarkdownBody(
data: '### Почта: __${detail!.email}__'),
MarkdownBody(
data: '### ФИО: __${detail!.receiver}__'),
],
),
detail?.payUrl == null
? const SizedBox.shrink()
: Center(
child: ElevatedButton(
onPressed: () async {
await _goToPage();
await payOrder(detail!.id);
},
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).primaryColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(50)),
),
foregroundColor: Colors.white,
fixedSize: const Size(180, 40),
),
child: const Text('Оплатить заказ'),
),
),
],
),
),
),
),
),
],
),
),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const GymLinkAppBar(),
body: detail == null
? const Center(child: CircularProgressIndicator())
: _buildContent(),
);
}
}

View File

@@ -19,6 +19,9 @@ class MyAppStateMobile extends State<MyApp> {
: MaterialApp( : MaterialApp(
title: 'GymLink Module', title: 'GymLink Module',
theme: theme, theme: theme,
themeMode: context.read<GymLinkProvider>().blackTheme
? ThemeMode.dark
: ThemeMode.light,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: const MainPage(), home: const MainPage(),
), ),

View File

@@ -13,7 +13,7 @@ ThemeData getThemeData(Color color, bool dark) {
).copyWith( ).copyWith(
onPrimary: dark ? materialColor[600] : Colors.white, onPrimary: dark ? materialColor[600] : Colors.white,
), ),
useMaterial3: true, // useMaterial3: true,
); );
} }

99
lib/tools/history.dart Normal file
View File

@@ -0,0 +1,99 @@
import 'dart:convert';
import 'dart:math';
import 'package:gymlink_module_web/interfaces/items.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<List<GymHistoryItem>> getHistory() async {
final prefs = await SharedPreferences.getInstance();
String historyString = prefs.getString('history') ?? "[]";
List<GymHistoryItem> history = [];
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
history.add(GymHistoryItem.fromJson(historyItem));
}
history.sort((a, b) => b.timestamp.compareTo(a.timestamp));
return history;
}
Future<void> addToHistory(GymHistoryItemDetail item) async {
final prefs = await SharedPreferences.getInstance();
String historyString = prefs.getString('history') ?? "[]";
List<GymHistoryItem> history = [];
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
history.add(GymHistoryItem.fromJson(historyItem));
}
item.id = Random().nextInt(100000).toString();
String detailHistoryString = prefs.getString('detail_history') ?? "[]";
List<GymHistoryItemDetail> detailHistory = [];
for (var historyItem in jsonDecode(detailHistoryString) as List<dynamic>) {
detailHistory.add(GymHistoryItemDetail.fromJson(historyItem));
}
List<Map<String, dynamic>> providers = [];
for (final provider in item.providers) {
providers.add(provider.toJson());
}
var json = {
"id": item.id,
"date": DateTime.now()
.toLocal()
.toString()
.split(' ')[0]
.replaceAll('-', '.')
.split('.')
.reversed
.join('.'),
"sum": item.sum,
"pay_url": item.providers.where((e) => e.status == 'Не оплачен').isNotEmpty
? 'https://example.org'
: null,
"receiver": item.receiver,
"email": item.email,
"address": item.address,
"providers": providers,
"timestamp": DateTime.now().millisecondsSinceEpoch.toString(),
};
final detailHistoryItem = GymHistoryItemDetail.fromJson(json);
detailHistory.add(detailHistoryItem);
history.add(GymHistoryItem(
date: detailHistoryItem.date,
id: detailHistoryItem.id,
photo: detailHistoryItem.providers[0].items[0].photo,
sum: detailHistoryItem.sum,
timestamp: detailHistoryItem.timestamp,
));
prefs.setString('history', jsonEncode(history));
prefs.setString('detail_history', jsonEncode(detailHistory));
}
Future<GymHistoryItemDetail?> getHistoryDetail(String id) async {
final prefs = await SharedPreferences.getInstance();
String historyString = prefs.getString('detail_history') ?? "[]";
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
if (GymHistoryItemDetail.fromJson(historyItem).id == id) {
return GymHistoryItemDetail.fromJson(historyItem);
}
}
return null;
}
Future<void> payOrder(String id) async {
final prefs = await SharedPreferences.getInstance();
String historyString = prefs.getString('detail_history') ?? "[]";
List<GymHistoryItemDetail> history = [];
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
history.add(GymHistoryItemDetail.fromJson(historyItem));
}
List<GymHistoryItemDetail> newHistory = [];
for (final historyItem in history) {
if (historyItem.id == id) {
for (final provider in historyItem.providers) {
if (provider.status == 'Не оплачен') {
provider.status = 'Оплачен';
}
}
historyItem.payUrl = null;
}
newHistory.add(historyItem);
}
prefs.setString('detail_history', jsonEncode(newHistory));
}

View File

@@ -45,6 +45,7 @@
<button id="token2">Token btn 2</button> <button id="token2">Token btn 2</button>
<button id="colorChangeBtnRed">Color btn Red</button> <button id="colorChangeBtnRed">Color btn Red</button>
<button id="colorChangeBtnBlue">Color btn Blue</button> <button id="colorChangeBtnBlue">Color btn Blue</button>
<button id="clearBtn">Clear</button>
<section class="contents"> <section class="contents">
<article> <article>
<div id="flutter_target"></div> <div id="flutter_target"></div>

View File

@@ -31,31 +31,38 @@
} }
const btn = document.getElementById('token'); const btn = document.getElementById('token');
btn.addEventListener('click', () => getToken('eeb42dcb-8e5b-4f21-825a-3fc7ada43445')); btn.addEventListener('click', () => {
localStorage.clear();
getToken('eeb42dcb-8e5b-4f21-825a-3fc7ada43445');
});
const btn2 = document.getElementById('token2'); const btn2 = document.getElementById('token2');
btn2.addEventListener('click', () => getToken('a8622a61-3142-487e-8db8-b6aebd4f04aa')); btn2.addEventListener('click', () => {
localStorage.clear();
getToken('a8622a61-3142-487e-8db8-b6aebd4f04aa');
});
let colorChangeBtnRed = document.getElementById('colorChangeBtnRed'); let colorChangeBtnRed = document.getElementById('colorChangeBtnRed');
colorChangeBtnRed.addEventListener('click', function () { colorChangeBtnRed.addEventListener('click', function () {
var hexColor = '#FF0000' var hexColor = '#FF0000'.substring(1);
.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#ff' + r + r + g + g + b + b)
.substring(1);
var numColor = parseInt(hexColor, 16); var numColor = parseInt(hexColor, 16);
appState.changeColor(numColor, true); appState.changeColor(numColor, true);
}); });
let colorChangeBtnBlue = document.getElementById('colorChangeBtnBlue'); let colorChangeBtnBlue = document.getElementById('colorChangeBtnBlue');
colorChangeBtnBlue.addEventListener('click', function () { colorChangeBtnBlue.addEventListener('click', function () {
var hexColor = '#0000FF' var hexColor = '#0000FF'.substring(1);
.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#ff' + r + r + g + g + b + b)
.substring(1);
var numColor = parseInt(hexColor, 16); var numColor = parseInt(hexColor, 16);
appState.changeColor(numColor, false); appState.changeColor(numColor, false);
}); });
let clearBtn = document.getElementById('clearBtn');
clearBtn.addEventListener('click', function () {
localStorage.clear();
window.location.reload();
});
function onError() { function onError() {
console.error('aboba'); console.error('Error');
} }
appState.setOnError(onError); appState.setOnError(onError);

View File

@@ -1,51 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Прием платежа с помощью виджета ЮKassa</title>
<!--Подключение библиотеки для инициализации виджета ЮKassa-->
<script src="https://yookassa.ru/checkout-widget/v1/checkout-widget.js"></script>
</head>
<body>
Ниже отобразится платежная форма. Если вы еще не создавали платеж и не передавали токен для инициализации виджета, появится сообщение об ошибке.
<!--Контейнер, в котором будет отображаться платежная форма-->
<div id="payment-form"></div>
Данные банковской карты для оплаты в <b>тестовом магазине</b>:
- номер — <b>5555 5555 5555 4477</b>
- срок действия — <b>01/30</b> (или другая дата, больше текущей)
- CVC — <b>123</b> (или три любые цифры)
- код для прохождения 3-D Secure — <b>123</b> (или три любые цифры)
<a href=https://yookassa.ru/developers/payment-acceptance/testing-and-going-live/testing#test-bank-card>Другие тестовые банковские карты</a>
<script>
//Инициализация виджета. Все параметры обязательные.
const checkout = new window.YooMoneyCheckoutWidget({
confirmation_token: 'ct-287e0c37-000f-5000-8000-16961d35b0fd', //Токен, который перед проведением оплаты нужно получить от ЮKassa
return_url: 'https://example.com/', //Ссылка на страницу завершения оплаты, это может быть любая ваша страница
//При необходимости можно изменить цвета виджета, подробные настройки см. в документации
//customization: {
//Настройка цветовой схемы, минимум один параметр, значения цветов в HEX
//colors: {
//Цвет акцентных элементов: кнопка Заплатить, выбранные переключатели, опции и текстовые поля
//control_primary: '#00BF96', //Значение цвета в HEX
//Цвет платежной формы и ее элементов
//background: '#F2F3F5' //Значение цвета в HEX
//}
//},
error_callback: function(error) {
console.log(error)
}
});
//Отображение платежной формы в контейнере
checkout.render('payment-form');
</script>
</body>
</html>