Initial commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
class InviteSignupScreen extends StatefulWidget {
|
||||
const InviteSignupScreen({super.key});
|
||||
@override
|
||||
State<InviteSignupScreen> createState() => _InviteSignupScreenState();
|
||||
}
|
||||
|
||||
class _InviteSignupScreenState extends State<InviteSignupScreen> {
|
||||
final _code = TextEditingController();
|
||||
final _email = TextEditingController();
|
||||
final _pass = TextEditingController();
|
||||
final _user = TextEditingController();
|
||||
|
||||
void _processSignup() async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final nav = Navigator.of(context);
|
||||
// 1. Check if Invite Code exists and is not used
|
||||
var codeDoc = await FirebaseFirestore.instance.collection('invite_codes').doc(_code.text.trim()).get();
|
||||
|
||||
final codeData = codeDoc.data();
|
||||
if (codeDoc.exists && codeData != null && codeData['isUsed'] == false) {
|
||||
try {
|
||||
// 2. Create the User
|
||||
UserCredential cred = await FirebaseAuth.instance.createUserWithEmailAndPassword(
|
||||
email: _email.text.trim(), password: _pass.text.trim());
|
||||
|
||||
// 3. Create Firestore Profile
|
||||
await FirebaseFirestore.instance.collection('users').doc(cred.user!.uid).set({
|
||||
'username': _user.text.trim(),
|
||||
'role': 'reader',
|
||||
'rankLevel': 1,
|
||||
'uid': cred.user!.uid,
|
||||
});
|
||||
|
||||
// 4. Burn the code
|
||||
await codeDoc.reference.update({'isUsed': true});
|
||||
|
||||
if (context.mounted) nav.pop();
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(content: Text(e.toString())));
|
||||
}
|
||||
} else {
|
||||
messenger.showSnackBar(const SnackBar(content: Text("Invalid or Used Code")));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("REDEEM INVITATION")),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
children: [
|
||||
TextField(controller: _code, decoration: const InputDecoration(labelText: "6-Digit Invite Code")),
|
||||
TextField(controller: _user, decoration: const InputDecoration(labelText: "Choose Username")),
|
||||
TextField(controller: _email, decoration: const InputDecoration(labelText: "Email")),
|
||||
TextField(controller: _pass, decoration: const InputDecoration(labelText: "Password"), obscureText: true),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton(onPressed: _processSignup, child: const Text("JOIN THE ORDER")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:onsolgo/core/constants.dart';
|
||||
import 'package:onsolgo/core/achievement_manager.dart';
|
||||
import 'package:onsolgo/screens/auth/tier_comparison_screen.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _email = TextEditingController();
|
||||
final _pass = TextEditingController();
|
||||
final _invite = TextEditingController();
|
||||
final _user = TextEditingController();
|
||||
bool _isSigningUp = false;
|
||||
bool _loading = false;
|
||||
|
||||
void _handleAuth() async {
|
||||
setState(() => _loading = true);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
if (_isSigningUp) {
|
||||
var codeDoc = await FirebaseFirestore.instance.collection('invite_codes').doc(_invite.text.trim()).get();
|
||||
if (!codeDoc.exists || codeDoc['isUsed'] == true) throw "Invalid or Used Invite Code";
|
||||
|
||||
UserCredential cred = await FirebaseAuth.instance.createUserWithEmailAndPassword(
|
||||
email: _email.text.trim(), password: _pass.text.trim());
|
||||
|
||||
await FirebaseFirestore.instance.collection('users').doc(cred.user!.uid).set({
|
||||
'username': _user.text.trim(),
|
||||
'role': 'reader',
|
||||
'tier': 'free', // DEFAULT TIER
|
||||
'rankLevel': 1,
|
||||
'uid': cred.user!.uid,
|
||||
'streak': 1,
|
||||
'lastVisit': FieldValue.serverTimestamp(),
|
||||
'pagesRead': 0,
|
||||
'chaptersRead': 0,
|
||||
'energy': 2,
|
||||
'lastEnergyRefill': FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
await codeDoc.reference.update({'isUsed': true});
|
||||
AchievementManager.unlock(cred.user!.uid, "initiate");
|
||||
} else {
|
||||
await FirebaseAuth.instance.signInWithEmailAndPassword(email: _email.text.trim(), password: _pass.text.trim());
|
||||
}
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(content: Text(e.toString())));
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
children: [
|
||||
CachedNetworkImage(imageUrl: kOnsolBanner, height: 80),
|
||||
const SizedBox(height: 40),
|
||||
if (_isSigningUp) TextField(controller: _invite, decoration: const InputDecoration(labelText: "INVITE CODE", labelStyle: TextStyle(color: kOnsolGold))),
|
||||
if (_isSigningUp) TextField(controller: _user, decoration: const InputDecoration(labelText: "CHOOSE USERNAME")),
|
||||
TextField(controller: _email, decoration: const InputDecoration(labelText: "EMAIL")),
|
||||
TextField(controller: _pass, decoration: const InputDecoration(labelText: "PASSWORD"), obscureText: true),
|
||||
const SizedBox(height: 30),
|
||||
_loading ? const CircularProgressIndicator(color: kOnsolGold) : ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 50), backgroundColor: kOnsolGold, foregroundColor: Colors.black),
|
||||
onPressed: _handleAuth,
|
||||
child: Text(_isSigningUp ? "JOIN THE ORDER" : "ENTER THE ORDER")
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// NEW TIER COMPARISON LINK
|
||||
TextButton(
|
||||
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (c) => const TierComparisonScreen())),
|
||||
child: const Text("View Tier Benefits & Features", style: TextStyle(color: kOnsolGold, fontSize: 12, decoration: TextDecoration.underline))
|
||||
),
|
||||
|
||||
TextButton(
|
||||
onPressed: () => setState(() => _isSigningUp = !_isSigningUp),
|
||||
child: Text(_isSigningUp ? "Already a Citizen? Login" : "Redeem Invite", style: const TextStyle(color: Colors.white70))
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:onsolgo/core/constants.dart';
|
||||
|
||||
class TierComparisonScreen extends StatelessWidget {
|
||||
const TierComparisonScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
title: const Text("ORDER TIERS", style: TextStyle(letterSpacing: 3, fontSize: 14)),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("CHOOSE YOUR STATUS",
|
||||
style: TextStyle(color: kOnsolGold, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 4)),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
_buildTierCard(
|
||||
context,
|
||||
name: "READER",
|
||||
price: "FREE",
|
||||
color: Colors.grey,
|
||||
features: [
|
||||
"Read current ManaA issues",
|
||||
"Engage with Our Artists on the Social Feed",
|
||||
"Shop the Merch Vault",
|
||||
"Unlock Citizen Milestones",
|
||||
],
|
||||
limitations: ["Standard Access (With Ads)", "Viewing only (No social posting)"],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildTierCard(
|
||||
context,
|
||||
name: "OGO+",
|
||||
price: "\$2.99 / mo",
|
||||
color: Colors.amber,
|
||||
features: [
|
||||
"Ad-Free Access to ManaA issues",
|
||||
"Post to the Social Feed: A Voice in the Community",
|
||||
"Download your Favorite Chapters for Offline Reading",
|
||||
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildTierCard(
|
||||
context,
|
||||
name: "OGO+ MAX",
|
||||
price: "\$4.99 / mo",
|
||||
color: kOnsolGold,
|
||||
features: [
|
||||
"Everything in OGO+ tier",
|
||||
"The Archive (Access to back issues)",
|
||||
"Exclusive Video Content",
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTierCard(BuildContext context, {
|
||||
required String name, required String price, required Color color,
|
||||
required List<String> features, List<String> limitations = const [],
|
||||
}) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[900],
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: color.withValues(alpha: 0.4), width: 1),
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(name, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 20, letterSpacing: 2)),
|
||||
Text(price, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
const Divider(color: Colors.white10, height: 30),
|
||||
...features.map((f) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(children: [
|
||||
Icon(Icons.check_circle, color: color, size: 16),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(f, style: const TextStyle(fontSize: 12, color: Colors.white70))),
|
||||
]),
|
||||
)),
|
||||
...limitations.map((l) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(children: [
|
||||
const Icon(Icons.info_outline, color: Colors.white24, size: 16),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(l, style: const TextStyle(fontSize: 12, color: Colors.white24))),
|
||||
]),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user