Skip to content

Commit bb202d8

Browse files
committed
urls page logic completed
1 parent 9a902a8 commit bb202d8

File tree

7 files changed

+267
-55
lines changed

7 files changed

+267
-55
lines changed

backend/controllers/urls.controllers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ const Store_Url = require('../models/url.models.js');
22

33
async function userUrls(req, res) {
44

5+
const userId = req.user.id;
56
try {
6-
const userId = req.user.id;
77
const urls = await Store_Url.find({ owner: userId }).sort({ createdAt: -1 });
88
res.status(200).json(urls);
99
} catch (error) {

flutter_app/lib/main.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_app/screens/details.screen.dart';
33
import 'package:flutter_app/screens/home_screen.dart';
4+
import 'package:flutter_app/screens/urls_screen.dart';
45
import 'screens/login_screen.dart';
56
import 'screens/signup_screen.dart';
67

@@ -19,14 +20,14 @@ class MyApp extends StatelessWidget {
1920
theme: ThemeData(
2021
scaffoldBackgroundColor: Colors.white10,
2122
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xffffbf00)),
22-
useMaterial3: true,
2323
),
2424
initialRoute: '/login',
2525
routes: {
2626
'/login': (context) => const LoginScreen(),
2727
'/signup': (context) => const SignupScreen(),
2828
'/home': (context) => const HomeScreen(),
29-
'/details': (context) => const DetailsScreen(longUrl: "hello", shortUrl: "https://sunjay.xyz/abc123")
29+
'/details': (context) => const DetailsScreen(longUrl: "hello", shortUrl: "https://sunjay.xyz/abc123"),
30+
'/urls': (context) => const UrlsScreen()
3031
},
3132
);
3233
}

flutter_app/lib/screens/home_screen.dart

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'dart:convert';
22

33
import 'package:flutter/material.dart';
4-
import 'package:flutter_app/screens/copy_container.dart';
5-
import 'package:flutter_app/screens/details.screen.dart';
4+
import 'package:flutter_app/widgets/copy_container.dart';
5+
import 'package:flutter_app/widgets/app_drawer.dart';
66
import 'package:http/http.dart' as http;
77
import 'package:shared_preferences/shared_preferences.dart';
88

@@ -76,60 +76,11 @@ class _HomeScreenState extends State<HomeScreen> {
7676
}
7777
}
7878

79-
void handleLogout() async {
80-
final prefs = await SharedPreferences.getInstance();
81-
await prefs.remove('JWT_TOKEN');
82-
83-
Navigator.pushReplacementNamed(context, '/login');
84-
}
85-
8679
@override
8780
Widget build(BuildContext context) {
8881
return Scaffold(
8982
appBar: AppBar(title: Text("Home"), centerTitle: true),
90-
drawer: Drawer(
91-
child: ListView(
92-
children: [
93-
SizedBox(height: 30),
94-
95-
ListTile(
96-
leading: Icon(Icons.home),
97-
title: Text("Home"),
98-
onTap: () {},
99-
),
100-
101-
ListTile(
102-
leading: Icon(Icons.link),
103-
title: Text("Urls"),
104-
onTap: () {},
105-
),
106-
107-
ListTile(
108-
leading: Icon(Icons.insert_chart),
109-
title: Text("Details"),
110-
onTap: () {
111-
Navigator.push(
112-
context,
113-
MaterialPageRoute(
114-
builder: (context) => DetailsScreen(
115-
longUrl: _urlController.text,
116-
shortUrl:
117-
"https://sunjay.xyz/abc123", // mock short link for now
118-
),
119-
),
120-
);
121-
},
122-
),
123-
124-
ListTile(
125-
leading: Icon(Icons.logout),
126-
title: Text("Logout"),
127-
onTap: handleLogout,
128-
),
129-
],
130-
),
131-
),
132-
83+
drawer: AppDrawer(),
13384
body: Form(
13485
key: formkey,
13586
child: Padding(
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import 'dart:convert';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_app/widgets/app_drawer.dart';
4+
import 'package:flutter_app/widgets/urlListTile.dart';
5+
import 'package:shared_preferences/shared_preferences.dart';
6+
import 'package:http/http.dart' as http;
7+
8+
class UrlsScreen extends StatefulWidget {
9+
const UrlsScreen({super.key});
10+
11+
@override
12+
State<UrlsScreen> createState() => _UrlsScreenState();
13+
}
14+
15+
class _UrlsScreenState extends State<UrlsScreen> {
16+
@override
17+
void initState() {
18+
super.initState();
19+
_checkToken();
20+
}
21+
22+
Future<String?> _getToken() async {
23+
final prefs = await SharedPreferences.getInstance();
24+
return prefs.getString('JWT_TOKEN');
25+
}
26+
27+
void _checkToken() async {
28+
final token = await _getToken();
29+
30+
if (token == null || token.isEmpty) {
31+
if (mounted) {
32+
Navigator.pushReplacementNamed(context, '/login');
33+
}
34+
}
35+
}
36+
37+
@override
38+
Widget build(BuildContext context) {
39+
return Scaffold(
40+
appBar: AppBar(title: const Text("Urls"), centerTitle: true),
41+
drawer: const AppDrawer(),
42+
body: Padding(
43+
padding: const EdgeInsets.all(20),
44+
child: Column(
45+
crossAxisAlignment: CrossAxisAlignment.stretch,
46+
children: [
47+
const SizedBox(height: 20),
48+
const Text(
49+
"See your short URLs below",
50+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
51+
textAlign: TextAlign.center,
52+
),
53+
const SizedBox(height: 20),
54+
55+
Expanded(child: UrlListSection()),
56+
],
57+
),
58+
),
59+
);
60+
}
61+
}
62+
63+
64+
class UrlListSection extends StatelessWidget {
65+
const UrlListSection({super.key});
66+
67+
Future<List<dynamic>> fetchUrls() async {
68+
final prefs = await SharedPreferences.getInstance();
69+
final token = prefs.getString('JWT_TOKEN');
70+
71+
if (token == null || token.isEmpty) {
72+
return [];
73+
}
74+
75+
try {
76+
final response = await http.get(
77+
Uri.parse('http://localhost:9000/api/userUrls/'),
78+
headers: {
79+
'Content-Type': 'application/json',
80+
'Authorization': 'Bearer $token',
81+
},
82+
);
83+
84+
if (response.statusCode == 200 || response.statusCode == 304) {
85+
final data = jsonDecode(response.body);
86+
return data;
87+
} else {
88+
throw Exception("Failed to fetch URLs (${response.statusCode})");
89+
}
90+
} catch (e) {
91+
debugPrint("Error fetching URLs: $e");
92+
throw Exception("Network error occurred");
93+
}
94+
}
95+
96+
@override
97+
Widget build(BuildContext context) {
98+
return FutureBuilder<List<dynamic>>(
99+
future: fetchUrls(),
100+
builder: (context, snapshot) {
101+
if (snapshot.connectionState == ConnectionState.waiting) {
102+
return const Center(child: CircularProgressIndicator());
103+
}
104+
else if (snapshot.hasError) {
105+
return Center(child: Text("Error: ${snapshot.error}"));
106+
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
107+
return const Center(child: Text("No URLs found"));
108+
}
109+
110+
final urls = snapshot.data!;
111+
return ListView.builder(
112+
itemCount: urls.length,
113+
itemBuilder: (context, index) {
114+
return UrlListTile(url: urls[index]);
115+
},
116+
);
117+
},
118+
);
119+
}
120+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
4+
class AppDrawer extends StatelessWidget {
5+
const AppDrawer({super.key});
6+
7+
Future<void> _handleLogout(BuildContext context) async {
8+
final prefs = await SharedPreferences.getInstance();
9+
await prefs.remove('JWT_TOKEN');
10+
11+
Navigator.pushReplacementNamed(context, '/login');
12+
13+
ScaffoldMessenger.of(context).showSnackBar(
14+
const SnackBar(content: Text("Logged out successfully")),
15+
);
16+
}
17+
18+
@override
19+
Widget build(BuildContext context) {
20+
return Drawer(
21+
child: ListView(
22+
children: [
23+
const SizedBox(height: 30),
24+
25+
ListTile(
26+
leading: const Icon(Icons.home),
27+
title: const Text("Home"),
28+
onTap: () {
29+
Navigator.pop(context);
30+
Navigator.pushReplacementNamed(context, '/home');
31+
},
32+
),
33+
34+
ListTile(
35+
leading: const Icon(Icons.link),
36+
title: const Text("Urls"),
37+
onTap: () {
38+
Navigator.pop(context);
39+
Navigator.pushNamed(context, '/urls');
40+
},
41+
),
42+
43+
ListTile(
44+
leading: const Icon(Icons.insert_chart),
45+
title: const Text("Details"),
46+
onTap: () {
47+
Navigator.pop(context);
48+
Navigator.pushNamed(context, '/details');
49+
},
50+
),
51+
52+
const Divider(),
53+
54+
ListTile(
55+
leading: const Icon(Icons.logout),
56+
title: const Text("Logout"),
57+
onTap: () => _handleLogout(context),
58+
),
59+
],
60+
),
61+
);
62+
}
63+
}
File renamed without changes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_app/screens/details.screen.dart';
3+
4+
class UrlListTile extends StatelessWidget {
5+
final Map<String, dynamic> url;
6+
7+
const UrlListTile({super.key, required this.url});
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
final name = url['name'];
12+
final shortUrl = url['shortId'];
13+
final createdAt = url['createdAt'];
14+
final visitHistory = url['visitHistory'] ?? [];
15+
final clicks = visitHistory.length;
16+
17+
return Card(
18+
color: Colors.white,
19+
elevation: 2,
20+
child: InkWell(
21+
borderRadius: BorderRadius.circular(10),
22+
onTap: () {
23+
Navigator.push(
24+
context,
25+
MaterialPageRoute(
26+
builder: (context) => DetailsScreen(
27+
longUrl: url["redirectUrl"],
28+
shortUrl: "http://localhost:9000/$shortUrl",
29+
),
30+
),
31+
);
32+
},
33+
child: Padding(
34+
padding: const EdgeInsets.all(12),
35+
child: Column(
36+
crossAxisAlignment: CrossAxisAlignment.start,
37+
children: [
38+
Row(
39+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
40+
children: [
41+
Text(
42+
'🔗 $name',
43+
style: const TextStyle(
44+
fontSize: 16,
45+
fontWeight: FontWeight.w600,
46+
color: Colors.black87,
47+
),
48+
),
49+
Text(
50+
createdAt,
51+
style: const TextStyle(fontSize: 12, color: Colors.grey),
52+
),
53+
],
54+
),
55+
56+
const SizedBox(height: 6),
57+
58+
Row(
59+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
60+
children: [
61+
Text(shortUrl),
62+
Row(
63+
children: [
64+
Icon(Icons.ads_click_outlined),
65+
const SizedBox(width: 4),
66+
Text('$clicks'),
67+
],
68+
),
69+
],
70+
),
71+
],
72+
),
73+
),
74+
),
75+
);
76+
}
77+
}

0 commit comments

Comments
 (0)