Signup and signing are ready
This commit is contained in:
parent
d47336e34e
commit
45a52d5410
52
lib/api/grpc/accounts.dart
Normal file
52
lib/api/grpc/accounts.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import 'package:grpc/grpc_web.dart';
|
||||||
|
import 'package:softplayer_dart_proto/accounts/accounts_v1.pbgrpc.dart';
|
||||||
|
import 'package:softplayer_dart_proto/main.dart';
|
||||||
|
|
||||||
|
class AccountsGrpc {
|
||||||
|
final GrpcWebClientChannel channel;
|
||||||
|
late AccountsClient accountsStub;
|
||||||
|
AccountsGrpc({
|
||||||
|
required this.channel,
|
||||||
|
});
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
accountsStub = AccountsClient(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signIn(String username, String email, String password) async {
|
||||||
|
final request = AccountWithPassword(
|
||||||
|
data: AccountData(
|
||||||
|
name: username,
|
||||||
|
email: email,
|
||||||
|
),
|
||||||
|
password: AccountPassword(
|
||||||
|
password: password,
|
||||||
|
));
|
||||||
|
try {
|
||||||
|
final response = await accountsStub.signIn(request);
|
||||||
|
print("$response");
|
||||||
|
return "1";
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signUp(String username, String email, String password) async {
|
||||||
|
final request = AccountWithPassword(
|
||||||
|
data: AccountData(
|
||||||
|
name: username,
|
||||||
|
email: email,
|
||||||
|
),
|
||||||
|
password: AccountPassword(
|
||||||
|
password: password,
|
||||||
|
));
|
||||||
|
try {
|
||||||
|
final response = await accountsStub.signUp(request);
|
||||||
|
print("$response");
|
||||||
|
return "1";
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,21 @@
|
|||||||
// This project is not supposed to be cross-platform,
|
// This project is not supposed to be cross-platform,
|
||||||
// so we don't care about this warning
|
// so we don't care about this warning
|
||||||
// ignore: avoid_web_libraries_in_flutter
|
// ignore: avoid_web_libraries_in_flutter
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:softplayer_web/api/grpc/accounts.dart';
|
||||||
import 'package:softplayer_web/components/sign_in_form.dart';
|
import 'package:softplayer_web/components/sign_in_form.dart';
|
||||||
import 'package:softplayer_web/components/sign_up_form.dart';
|
import 'package:softplayer_web/components/sign_up_form.dart';
|
||||||
|
|
||||||
class MenuPanel extends StatefulWidget implements PreferredSizeWidget {
|
class MenuPanel extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final TabName tab;
|
final TabName tab;
|
||||||
const MenuPanel({super.key, required this.tab})
|
final AccountsGrpc accountsGrpc;
|
||||||
: preferredSize = const Size.fromHeight(kToolbarHeight);
|
const MenuPanel({
|
||||||
|
super.key,
|
||||||
|
required this.tab,
|
||||||
|
required this.accountsGrpc,
|
||||||
|
}) : preferredSize = const Size.fromHeight(kToolbarHeight);
|
||||||
@override
|
@override
|
||||||
final Size preferredSize;
|
final Size preferredSize;
|
||||||
@override
|
@override
|
||||||
@ -37,14 +42,18 @@ class _MenuPanel extends State<MenuPanel> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => const SignInForm());
|
builder: (BuildContext context) => SignInForm(
|
||||||
|
accountsGrpc: widget.accountsGrpc,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
child: const Text("sign in")),
|
child: const Text("sign in")),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => const SignUpForm());
|
builder: (BuildContext context) => SignUpForm(
|
||||||
|
accountsGrpc: widget.accountsGrpc,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
child: const Text("sign up")),
|
child: const Text("sign up")),
|
||||||
];
|
];
|
||||||
|
@ -1,64 +1,98 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:grpc/grpc_web.dart';
|
||||||
|
import 'package:softplayer_web/api/grpc/accounts.dart';
|
||||||
|
|
||||||
class SignInForm extends StatefulWidget {
|
class SignInForm extends StatefulWidget {
|
||||||
const SignInForm({super.key});
|
const SignInForm({
|
||||||
|
super.key,
|
||||||
|
required this.accountsGrpc,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AccountsGrpc accountsGrpc;
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _SignInFormState();
|
State<StatefulWidget> createState() => _SignInFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SignInFormState extends State<SignInForm> {
|
class _SignInFormState extends State<SignInForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final usernameCtrl = TextEditingController();
|
||||||
|
final passwordCtrl = TextEditingController();
|
||||||
static const dialogName = "Sign In";
|
static const dialogName = "Sign In";
|
||||||
|
void submitForm() {
|
||||||
|
// Validate returns true if the form is valid, or false otherwise.
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
final username = usernameCtrl.text;
|
||||||
|
final password = passwordCtrl.text;
|
||||||
|
widget.accountsGrpc
|
||||||
|
.signIn(username, "", password)
|
||||||
|
.then((value) => null)
|
||||||
|
.catchError((e) {
|
||||||
|
GrpcError error = e;
|
||||||
|
String msg;
|
||||||
|
if (error.message != null) {
|
||||||
|
msg = error.message!;
|
||||||
|
} else {
|
||||||
|
msg = error.toString();
|
||||||
|
}
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(msg),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
showCloseIcon: true,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
));
|
||||||
|
passwordCtrl.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => AlertDialog(
|
Widget build(BuildContext context) => AlertDialog(
|
||||||
title: const Text(dialogName),
|
title: const Text(dialogName),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 420,
|
width: 420,
|
||||||
height: 140,
|
height: 140,
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
autofocus: true,
|
controller: usernameCtrl,
|
||||||
decoration: const InputDecoration(
|
autofocus: true,
|
||||||
hintText: "Enter your username or email",
|
decoration: const InputDecoration(
|
||||||
icon: Icon(Icons.account_circle),
|
hintText: "Enter your username or email",
|
||||||
label: Text("Username"),
|
icon: Icon(Icons.account_circle),
|
||||||
),
|
label: Text("Username"),
|
||||||
cursorWidth: 1,
|
),
|
||||||
cursorHeight: 18,
|
cursorWidth: 1,
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorHeight: 18,
|
||||||
),
|
cursorRadius: const Radius.circular(10),
|
||||||
TextFormField(
|
),
|
||||||
obscureText: true,
|
TextFormField(
|
||||||
decoration: const InputDecoration(
|
controller: passwordCtrl,
|
||||||
hintText: "Enter your password",
|
obscureText: true,
|
||||||
icon: Icon(Icons.password),
|
decoration: const InputDecoration(
|
||||||
label: Text("Password")
|
hintText: "Enter your password",
|
||||||
),
|
icon: Icon(Icons.password),
|
||||||
cursorWidth: 1,
|
label: Text("Password")),
|
||||||
cursorHeight: 18,
|
cursorWidth: 1,
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorHeight: 18,
|
||||||
),
|
cursorRadius: const Radius.circular(10),
|
||||||
])))),
|
onFieldSubmitted: (v) {
|
||||||
|
if (usernameCtrl.text.isEmpty) {
|
||||||
|
FocusScope.of(context).nextFocus();
|
||||||
|
} else {
|
||||||
|
submitForm();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
])))),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: submitForm,
|
||||||
// Validate returns true if the form is valid, or false otherwise.
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
// If the form is valid, display a snackbar. In the real world,
|
|
||||||
// you'd often call a server or save the information in a database.
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(_formKey.toString())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,86 +1,120 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:grpc/grpc_web.dart';
|
||||||
|
import 'package:softplayer_web/api/grpc/accounts.dart';
|
||||||
|
|
||||||
class SignUpForm extends StatefulWidget {
|
class SignUpForm extends StatefulWidget {
|
||||||
const SignUpForm({super.key});
|
const SignUpForm({
|
||||||
|
super.key,
|
||||||
|
required this.accountsGrpc,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
final AccountsGrpc accountsGrpc;
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _SignUpFormState();
|
State<StatefulWidget> createState() => _SignUpFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SignUpFormState extends State<SignUpForm> {
|
class _SignUpFormState extends State<SignUpForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
final usernameCtrl = TextEditingController();
|
||||||
|
final passwordCtrl = TextEditingController();
|
||||||
|
final passwordVerifyCtrl = TextEditingController();
|
||||||
|
final emailCtrl = TextEditingController();
|
||||||
|
|
||||||
static const dialogName = "Sign Up";
|
static const dialogName = "Sign Up";
|
||||||
|
|
||||||
|
void submitForm() {
|
||||||
|
// Validate returns true if the form is valid, or false otherwise.
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
final username = usernameCtrl.text;
|
||||||
|
final password = passwordCtrl.text;
|
||||||
|
final email = emailCtrl.text;
|
||||||
|
widget.accountsGrpc
|
||||||
|
.signUp(username, email, password)
|
||||||
|
.then((value) => null)
|
||||||
|
.catchError((e) {
|
||||||
|
GrpcError error = e;
|
||||||
|
String msg;
|
||||||
|
if (error.message != null) {
|
||||||
|
msg = error.message!;
|
||||||
|
} else {
|
||||||
|
msg = error.toString();
|
||||||
|
}
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(msg),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
showCloseIcon: true,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
));
|
||||||
|
passwordCtrl.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => AlertDialog(
|
Widget build(BuildContext context) => AlertDialog(
|
||||||
title: const Text(dialogName),
|
title: const Text(dialogName),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 420,
|
width: 420,
|
||||||
height: 280,
|
height: 280,
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
decoration: const InputDecoration(
|
controller: usernameCtrl,
|
||||||
hintText: "Enter your username",
|
decoration: const InputDecoration(
|
||||||
icon: Icon(Icons.account_circle),
|
hintText: "Enter your username",
|
||||||
label: Text("Username"),
|
icon: Icon(Icons.account_circle),
|
||||||
),
|
label: Text("Username"),
|
||||||
cursorWidth: 1,
|
),
|
||||||
cursorHeight: 18,
|
cursorWidth: 1,
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorHeight: 18,
|
||||||
),
|
cursorRadius: const Radius.circular(10),
|
||||||
TextFormField(
|
),
|
||||||
autofocus: true,
|
TextFormField(
|
||||||
decoration: const InputDecoration(
|
controller: emailCtrl,
|
||||||
hintText: "Enter your email",
|
autofocus: true,
|
||||||
icon: Icon(Icons.email),
|
decoration: const InputDecoration(
|
||||||
label: Text("Email"),
|
hintText: "Enter your email",
|
||||||
),
|
icon: Icon(Icons.email),
|
||||||
cursorWidth: 1,
|
label: Text("Email"),
|
||||||
cursorHeight: 18,
|
),
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorWidth: 1,
|
||||||
),
|
cursorHeight: 18,
|
||||||
TextFormField(
|
cursorRadius: const Radius.circular(10),
|
||||||
obscureText: true,
|
),
|
||||||
decoration: const InputDecoration(
|
TextFormField(
|
||||||
hintText: "Enter your password",
|
controller: passwordCtrl,
|
||||||
icon: Icon(Icons.password),
|
obscureText: true,
|
||||||
label: Text("Password")
|
decoration: const InputDecoration(
|
||||||
),
|
hintText: "Enter your password",
|
||||||
cursorWidth: 1,
|
icon: Icon(Icons.password),
|
||||||
cursorHeight: 18,
|
label: Text("Password")),
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorWidth: 1,
|
||||||
),
|
cursorHeight: 18,
|
||||||
TextFormField(
|
cursorRadius: const Radius.circular(10),
|
||||||
obscureText: true,
|
),
|
||||||
decoration: const InputDecoration(
|
TextFormField(
|
||||||
hintText: "Verify your password",
|
controller: passwordVerifyCtrl,
|
||||||
icon: Icon(Icons.password),
|
obscureText: true,
|
||||||
label: Text("Confirm Password")
|
decoration: const InputDecoration(
|
||||||
),
|
hintText: "Verify your password",
|
||||||
cursorWidth: 1,
|
icon: Icon(Icons.password),
|
||||||
cursorHeight: 18,
|
label: Text("Confirm Password")),
|
||||||
cursorRadius: const Radius.circular(10),
|
cursorWidth: 1,
|
||||||
),
|
cursorHeight: 18,
|
||||||
])))),
|
cursorRadius: const Radius.circular(10),
|
||||||
|
),
|
||||||
|
])))),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: submitForm,
|
||||||
// Validate returns true if the form is valid, or false otherwise.
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
// If the form is valid, display a snackbar. Up the real world,
|
|
||||||
// you'd often call a server or save the information in a database.
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(_formKey.toString())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:grpc/grpc_web.dart';
|
import 'package:grpc/grpc_web.dart';
|
||||||
|
import 'package:softplayer_web/api/grpc/accounts.dart';
|
||||||
import 'package:softplayer_web/components/menubar.dart';
|
import 'package:softplayer_web/components/menubar.dart';
|
||||||
import 'package:softplayer_web/helpers/page_wrapper.dart';
|
import 'package:softplayer_web/helpers/page_wrapper.dart';
|
||||||
import 'package:softplayer_web/pages/about.dart';
|
import 'package:softplayer_web/pages/about.dart';
|
||||||
@ -9,7 +10,7 @@ import 'package:softplayer_web/pages/home.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
const String backendURL = String.fromEnvironment(
|
const String backendURL = String.fromEnvironment(
|
||||||
'SOFTPLAYER_BACKEND_URL',
|
'SOFTPLAYER_BACKEND_URL',
|
||||||
defaultValue: 'http://softplayer.badhouseplants.net:8080',
|
defaultValue: 'https://softplayer-backend.badhouseplants.net:8080',
|
||||||
);
|
);
|
||||||
GrpcWebClientChannel grpcChannel =
|
GrpcWebClientChannel grpcChannel =
|
||||||
GrpcWebClientChannel.xhr(Uri.parse(backendURL));
|
GrpcWebClientChannel.xhr(Uri.parse(backendURL));
|
||||||
@ -18,28 +19,36 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key, required this.channel});
|
MyApp({super.key, required this.channel});
|
||||||
|
|
||||||
// A channel that should be used to fire grpc calls
|
|
||||||
final GrpcWebClientChannel channel;
|
final GrpcWebClientChannel channel;
|
||||||
|
late final AccountsGrpc accountsGrpc = AccountsGrpc(channel: channel);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
accountsGrpc.init();
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Softplayer',
|
title: 'Softplayer',
|
||||||
routes: {
|
routes: {
|
||||||
'/': (context) => const PageWrapper(
|
'/': (context) => PageWrapper(
|
||||||
appBar: MenuPanel(tab: TabName.home),
|
appBar: MenuPanel(
|
||||||
child: HomePage(),
|
tab: TabName.home,
|
||||||
|
accountsGrpc: accountsGrpc,
|
||||||
|
),
|
||||||
|
child: const HomePage(),
|
||||||
),
|
),
|
||||||
'/catalog': (context) => const PageWrapper(
|
'/catalog': (context) => PageWrapper(
|
||||||
appBar: MenuPanel(tab: TabName.catalog),
|
appBar: MenuPanel(
|
||||||
child: CatalogPage(),
|
tab: TabName.catalog,
|
||||||
|
accountsGrpc: accountsGrpc,
|
||||||
|
),
|
||||||
|
child: const CatalogPage(),
|
||||||
),
|
),
|
||||||
'/about': (context) => const PageWrapper(
|
'/about': (context) => PageWrapper(
|
||||||
appBar: MenuPanel(tab: TabName.about),
|
appBar: MenuPanel(
|
||||||
child: AboutPage(),
|
tab: TabName.about,
|
||||||
|
accountsGrpc: accountsGrpc,
|
||||||
|
),
|
||||||
|
child: const AboutPage(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
Loading…
Reference in New Issue
Block a user