Skip to content

Commit cdf2bac

Browse files
committed
Create account page and Update NavBar
1 parent f49913f commit cdf2bac

File tree

3 files changed

+449
-0
lines changed

3 files changed

+449
-0
lines changed

src/app/account/page.jsx

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { auth, db } from "@/firebase/config";
5+
import { updatePassword, deleteUser, reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth";
6+
import { doc, getDoc, deleteDoc } from "firebase/firestore";
7+
import {
8+
FaKey,
9+
FaUserCog,
10+
FaTrash,
11+
FaCopy,
12+
FaEye,
13+
FaEyeSlash,
14+
FaExclamationTriangle
15+
} from "react-icons/fa";
16+
17+
export default function AccountPage() {
18+
const [currentPassword, setCurrentPassword] = useState("");
19+
const [newPassword, setNewPassword] = useState("");
20+
const [confirmPassword, setConfirmPassword] = useState("");
21+
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
22+
const [showNewPassword, setShowNewPassword] = useState(false);
23+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
24+
const [publicKey, setPublicKey] = useState("");
25+
const [privateKey, setPrivateKey] = useState("");
26+
const [loading, setLoading] = useState(false);
27+
const [message, setMessage] = useState({ text: "", type: "" });
28+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
29+
30+
useEffect(() => {
31+
const fetchKeys = async () => {
32+
if (!auth.currentUser) return;
33+
34+
try {
35+
const userDoc = await getDoc(doc(db, "users", auth.currentUser.uid));
36+
if (userDoc.exists()) {
37+
const data = userDoc.data();
38+
setPublicKey(data.publicKey || "");
39+
setPrivateKey(data.privateKey || "");
40+
}
41+
} catch (error) {
42+
console.error("Error fetching keys:", error);
43+
setMessage({ text: "Failed to load keys", type: "error" });
44+
}
45+
};
46+
47+
fetchKeys();
48+
}, []);
49+
50+
const handleCopy = async (text, label) => {
51+
try {
52+
await navigator.clipboard.writeText(text);
53+
setMessage({ text: `${label} copied to clipboard!`, type: "success" });
54+
setTimeout(() => setMessage({ text: "", type: "" }), 3000);
55+
} catch (err) {
56+
setMessage({ text: "Failed to copy to clipboard", type: "error" });
57+
}
58+
};
59+
60+
const handlePasswordChange = async (e) => {
61+
e.preventDefault();
62+
if (newPassword !== confirmPassword) {
63+
setMessage({ text: "New passwords don't match", type: "error" });
64+
return;
65+
}
66+
67+
setLoading(true);
68+
try {
69+
const user = auth.currentUser;
70+
const credential = EmailAuthProvider.credential(
71+
user.email,
72+
currentPassword
73+
);
74+
await reauthenticateWithCredential(user, credential);
75+
await updatePassword(user, newPassword);
76+
77+
setMessage({ text: "Password updated successfully!", type: "success" });
78+
setCurrentPassword("");
79+
setNewPassword("");
80+
setConfirmPassword("");
81+
} catch (error) {
82+
console.error("Error updating password:", error);
83+
setMessage({ text: "Failed to update password: " + error.message, type: "error" });
84+
} finally {
85+
setLoading(false);
86+
}
87+
};
88+
89+
const handleDeleteAccount = async () => {
90+
if (!currentPassword) {
91+
setMessage({ text: "Please enter your current password", type: "error" });
92+
return;
93+
}
94+
95+
setLoading(true);
96+
try {
97+
const user = auth.currentUser;
98+
const credential = EmailAuthProvider.credential(
99+
user.email,
100+
currentPassword
101+
);
102+
await reauthenticateWithCredential(user, credential);
103+
104+
// Delete user data from Firestore
105+
await deleteDoc(doc(db, "users", user.uid));
106+
107+
// Delete user account
108+
await deleteUser(user);
109+
110+
// Redirect to home page
111+
window.location.href = "/";
112+
} catch (error) {
113+
console.error("Error deleting account:", error);
114+
setMessage({ text: "Failed to delete account: " + error.message, type: "error" });
115+
} finally {
116+
setLoading(false);
117+
}
118+
};
119+
120+
return (
121+
<div className="flex items-center justify-center py-12 px-6">
122+
<div className="bg-[#111827] rounded-2xl shadow-xl p-10 max-w-xl w-full">
123+
<h1 className="text-3xl font-bold mb-6 text-center text-white flex items-center justify-center">
124+
Account Settings <FaUserCog className="ml-3 w-8 h-8" />
125+
</h1>
126+
127+
{message.text && (
128+
<div
129+
className={`mb-4 p-3 rounded ${
130+
message.type === "error"
131+
? "bg-red-500/10 text-red-500"
132+
: "bg-green-500/10 text-green-500"
133+
}`}
134+
>
135+
{message.text}
136+
</div>
137+
)}
138+
139+
{/* Keys Section */}
140+
<div className="mb-8">
141+
<h2 className="text-xl font-semibold mb-4 text-white flex items-center">
142+
<FaKey className="w-5 h-5 mr-2" />
143+
Your Keys
144+
</h2>
145+
146+
<div className="space-y-4">
147+
<div>
148+
<label className="block text-sm font-medium text-gray-300 mb-2">Public Key</label>
149+
<div className="relative">
150+
<textarea
151+
readOnly
152+
value={publicKey}
153+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white text-sm font-mono"
154+
rows={3}
155+
/>
156+
{publicKey && (
157+
<button
158+
onClick={() => handleCopy(publicKey, "Public key")}
159+
className="absolute top-2 right-2 text-gray-400 hover:text-white"
160+
>
161+
<FaCopy />
162+
</button>
163+
)}
164+
</div>
165+
</div>
166+
167+
<div>
168+
<label className="block text-sm font-medium text-gray-300 mb-2">Private Key</label>
169+
<div className="relative">
170+
<textarea
171+
readOnly
172+
value={privateKey}
173+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white text-sm font-mono"
174+
rows={3}
175+
/>
176+
{privateKey && (
177+
<button
178+
onClick={() => handleCopy(privateKey, "Private key")}
179+
className="absolute top-2 right-2 text-gray-400 hover:text-white"
180+
>
181+
<FaCopy />
182+
</button>
183+
)}
184+
</div>
185+
</div>
186+
</div>
187+
</div>
188+
189+
{/* Change Password Section */}
190+
<div className="mb-8">
191+
<h2 className="text-xl font-semibold mb-4 text-white">Change Password</h2>
192+
<form onSubmit={handlePasswordChange} className="space-y-4">
193+
<div>
194+
<label className="block text-sm font-medium text-gray-300 mb-2">Current Password</label>
195+
<div className="relative">
196+
<input
197+
type={showCurrentPassword ? "text" : "password"}
198+
value={currentPassword}
199+
onChange={(e) => setCurrentPassword(e.target.value)}
200+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white"
201+
/>
202+
<button
203+
type="button"
204+
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
205+
className="absolute right-2 top-2.5 text-gray-400 hover:text-white"
206+
>
207+
{showCurrentPassword ? <FaEyeSlash /> : <FaEye />}
208+
</button>
209+
</div>
210+
</div>
211+
212+
<div>
213+
<label className="block text-sm font-medium text-gray-300 mb-2">New Password</label>
214+
<div className="relative">
215+
<input
216+
type={showNewPassword ? "text" : "password"}
217+
value={newPassword}
218+
onChange={(e) => setNewPassword(e.target.value)}
219+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white"
220+
/>
221+
<button
222+
type="button"
223+
onClick={() => setShowNewPassword(!showNewPassword)}
224+
className="absolute right-2 top-2.5 text-gray-400 hover:text-white"
225+
>
226+
{showNewPassword ? <FaEyeSlash /> : <FaEye />}
227+
</button>
228+
</div>
229+
</div>
230+
231+
<div>
232+
<label className="block text-sm font-medium text-gray-300 mb-2">Confirm New Password</label>
233+
<div className="relative">
234+
<input
235+
type={showConfirmPassword ? "text" : "password"}
236+
value={confirmPassword}
237+
onChange={(e) => setConfirmPassword(e.target.value)}
238+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white"
239+
/>
240+
<button
241+
type="button"
242+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
243+
className="absolute right-2 top-2.5 text-gray-400 hover:text-white"
244+
>
245+
{showConfirmPassword ? <FaEyeSlash /> : <FaEye />}
246+
</button>
247+
</div>
248+
</div>
249+
250+
<button
251+
type="submit"
252+
disabled={loading}
253+
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg transition"
254+
>
255+
{loading ? "Updating..." : "Update Password"}
256+
</button>
257+
</form>
258+
</div>
259+
260+
{/* Delete Account Section */}
261+
<div>
262+
<h2 className="text-xl font-semibold mb-4 text-white flex items-center">
263+
<FaTrash className="w-5 h-5 mr-2" />
264+
Delete Account
265+
</h2>
266+
267+
{!showDeleteConfirm ? (
268+
<button
269+
onClick={() => setShowDeleteConfirm(true)}
270+
className="w-full bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg transition flex items-center justify-center"
271+
>
272+
<FaExclamationTriangle className="mr-2" />
273+
Delete Account
274+
</button>
275+
) : (
276+
<div className="space-y-4">
277+
<p className="text-red-400 text-sm">
278+
This action cannot be undone. Please enter your password to confirm.
279+
</p>
280+
<div className="relative">
281+
<input
282+
type={showCurrentPassword ? "text" : "password"}
283+
value={currentPassword}
284+
onChange={(e) => setCurrentPassword(e.target.value)}
285+
placeholder="Enter your password"
286+
className="w-full p-2 bg-gray-900 border border-gray-700 rounded text-white"
287+
/>
288+
<button
289+
type="button"
290+
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
291+
className="absolute right-2 top-2.5 text-gray-400 hover:text-white"
292+
>
293+
{showCurrentPassword ? <FaEyeSlash /> : <FaEye />}
294+
</button>
295+
</div>
296+
<div className="flex space-x-4">
297+
<button
298+
onClick={() => {
299+
setShowDeleteConfirm(false);
300+
setCurrentPassword("");
301+
}}
302+
className="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-2 rounded-lg transition"
303+
>
304+
Cancel
305+
</button>
306+
<button
307+
onClick={handleDeleteAccount}
308+
disabled={loading}
309+
className="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg transition"
310+
>
311+
{loading ? "Deleting..." : "Confirm Delete"}
312+
</button>
313+
</div>
314+
</div>
315+
)}
316+
</div>
317+
</div>
318+
</div>
319+
);
320+
}

src/app/layout.jsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import './globals.css';
2+
import Navbar from '@/components/Navbar';
3+
4+
export const metadata = {
5+
title: 'CryptoSite',
6+
description: 'Secure cryptography utility for file encryption, decryption, and key management',
7+
};
8+
9+
export default function RootLayout({ children }) {
10+
return (
11+
<html lang="en">
12+
<body className="bg-gray-900 min-h-screen">
13+
<Navbar />
14+
<main>
15+
{children}
16+
</main>
17+
</body>
18+
</html>
19+
);
20+
}

0 commit comments

Comments
 (0)