Skip to content

Commit 7eb40d9

Browse files
committed
add login feature
1 parent 877d773 commit 7eb40d9

6 files changed

Lines changed: 170 additions & 31 deletions

File tree

dist/settings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings.js.LICENSE.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ PERFORMANCE OF THIS SOFTWARE.
4242

4343
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
4444

45+
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
46+
4547
/**
4648
* @license React
4749
* react-dom-client.production.js

src/settings/settings.jsx

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import About from "./settings_components/about.jsx";
88
import NotesList from "./settings_components/NotesList.jsx";
99
import UserGuide from "./settings_components/user_guide.jsx";
1010
import Features from "./settings_components/features.jsx";
11+
import { toast, ToastContainer } from "react-toastify";
1112

1213
function Settings() {
1314
const [darkMode, setDarkMode] = useState();
@@ -16,12 +17,15 @@ function Settings() {
1617
const [sortOption, setSortOption] = useState("date-desc");
1718
const [activeTab, setActiveTab] = useState("settings");
1819
const [hideSortNotes, setHideSortNotes] = useState(false);
19-
const [userProfile, setUserProfile] = useState(null);
2020
const [showHelp, setShowHelp] = useState(true);
2121

2222

23-
useEffect(() => {
24-
chrome.storage.local.get([ "settings", "userProfile"], (result) => {
23+
useEffect(() => {
24+
chrome.storage.local.get([ "settings", "idToken", "accessToken" ], (result) => {
25+
const loggedIn = !!result.idToken && !!result.accessToken;
26+
if (!loggedIn) {
27+
toast.warning("Please login to save notes on cloud");
28+
}
2529
if (result.settings !== undefined) {
2630
setDarkMode(result.settings.darkMode);
2731
setDetailedView(result.settings.detailedView);
@@ -31,7 +35,6 @@ function Settings() {
3135
if (result.settings.popupSize) setPopupSize(result.settings.popupSize);
3236
if (result.settings.sortOption) setSortOption(result.settings.sortOption);
3337
if (result.settings.hideSortNotes) setHideSortNotes(result.settings.hideSortNotes || false);
34-
if (result.userProfile !== undefined) setUserProfile(result.userProfile);
3538
}
3639
});
3740
}, []);
@@ -247,6 +250,16 @@ function Settings() {
247250

248251
return (
249252
<div className="d-flex">
253+
<ToastContainer
254+
position="top-right"
255+
autoClose={3000}
256+
hideProgressBar
257+
newestOnTop
258+
closeOnClick
259+
pauseOnHover
260+
draggable
261+
/>
262+
250263
<div className="settings-tabs">
251264
<div className="nav flex-column nav-pills">
252265
<button
@@ -292,29 +305,22 @@ function Settings() {
292305
className={`nav-link profile-section d-flex align-items-center gap-2 ${activeTab === "profile" ? "active" : ""}`}
293306
onClick={() => setActiveTab("profile")}
294307
>
295-
{userProfile && userProfile.picture ? (
296-
<>
297-
<img
298-
src={userProfile.picture}
299-
alt="User"
300-
width="24"
301-
height="24"
302-
style={{ borderRadius: "50%" }}
303-
/>
304-
<span>{userProfile.name.split(" ")[0]}</span>
305-
</>
306-
) : (<>
308+
<>
307309
<i className="fas fa-user-circle"></i>
308310
<span>Profile</span>
309-
</>)}
311+
</>
310312
</div>
311313
</div>
312314

313315
<div className="container settings-layout mt-4">
314-
<h2>
315-
{activeTab.charAt(0).toUpperCase() +
316-
activeTab.slice(1).replace("-", " ")}
317-
</h2>
316+
<div className="d-flex justify-content-between">
317+
<h2 className="d-inline">
318+
{activeTab.charAt(0).toUpperCase() +
319+
activeTab.slice(1).replace("-", " ")}
320+
</h2>
321+
322+
</div>
323+
318324
{renderTabContent()}
319325
</div>
320326

src/settings/settings_components/NotesList.jsx

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, {useState, useEffect, useRef} from "react";
22
import sanitizeHtml from "sanitize-html";
33
import RichTextEditor from "../../components/RichText.jsx";
44
import { deleteNoteById, permanentlyDeleteNoteById } from "../utils/commonFunctions.js";
5+
import { toast } from "react-toastify";
6+
import "react-toastify/dist/ReactToastify.css";
57

68
const NotesList = () => {
79
const [viewMode, setViewMode] = useState("list");
@@ -24,6 +26,7 @@ const NotesList = () => {
2426
notesText: '',
2527
tag: "",
2628
pinned: "all",
29+
deletedStatus: "active",
2730
});
2831

2932
const handleFilterChange = (field, value) => {
@@ -173,7 +176,7 @@ const NotesList = () => {
173176
icon.classList.remove("copy-icon-green");
174177
}, 1000);
175178
})
176-
.catch(err => console.error("Error copying text: ", err));
179+
.catch(err => console.log("Error copying text: ", err));
177180
};
178181

179182
const handleSortOptionChange = (e) => {
@@ -246,7 +249,7 @@ const NotesList = () => {
246249
if (filters.pinnedStatus === "unpinned" && note.pinned) return false;
247250

248251
if (filters.deletedStatus === "deleted" && !note.deleted) return false;
249-
if (filters.deletedStatus === "not_deleted" && note.deleted) return false;
252+
if (filters.deletedStatus === "active" && note.deleted) return false;
250253

251254
return true;
252255
};
@@ -376,6 +379,59 @@ const NotesList = () => {
376379
document.execCommand(command, false, null);
377380
};
378381

382+
function getUserIdFromIdToken(idToken) {
383+
const payload = JSON.parse(atob(idToken.split(".")[1]));
384+
return payload.sub;
385+
}
386+
387+
const handleSyncClick = async () => {
388+
try {
389+
chrome.storage.local.get(["idToken", "notes"], async (result) => {
390+
const idToken = result.idToken;
391+
const notes = result.notes || [];
392+
393+
if (!idToken) {
394+
console.log("Missing idToken");
395+
toast.warning("First Need to login");
396+
397+
return;
398+
}
399+
400+
const userId = getUserIdFromIdToken(idToken);
401+
402+
await syncNotesToAWS(idToken, userId);
403+
});
404+
} catch (err) {
405+
console.log("Sync error:", err);
406+
}
407+
};
408+
409+
async function syncNotesToAWS(idToken, userId) {
410+
chrome.storage.local.get(["notes"], async (result) => {
411+
const localNotes = result.notes || [];
412+
413+
if (localNotes.length > 0) {
414+
try {
415+
const response = await fetch(
416+
process.env.LAMBDA_URL + "sync_notes",
417+
{
418+
method: "POST",
419+
headers: {
420+
"Content-Type": "application/json",
421+
Authorization: idToken
422+
},
423+
body: JSON.stringify({ userId, notes: localNotes })
424+
}
425+
);
426+
toast.success("Local Notes saved successfully to cloud!");
427+
} catch (err) {
428+
toast.error("Failed to save notes. Try again after some time");
429+
console.log("Sync failed:", err);
430+
}
431+
}
432+
});
433+
}
434+
379435
return (
380436
<div className="notes-view card border-1">
381437
<div className="notes-header mb-4 p-3 border-bottom">
@@ -394,6 +450,14 @@ const NotesList = () => {
394450
</div>
395451
</div>
396452
<div className="d-flex gap-3 align-items-center">
453+
<button
454+
className="btn btn-outline-success btn-sm flex-sm-shrink-0"
455+
onClick={handleSyncClick}
456+
>
457+
<i className="fas fa-cloud-upload-alt me-2"></i>
458+
Sync
459+
</button>
460+
397461
<button
398462
className="btn btn-outline-primary btn-sm flex-sm-shrink-0"
399463
onClick={openFilterModal}
@@ -512,7 +576,7 @@ const NotesList = () => {
512576
>
513577
<option value="both">Both</option>
514578
<option value="deleted">Deleted</option>
515-
<option value="not_deleted">Not Deleted</option>
579+
<option value="active">Active</option>
516580
</select>
517581
</div>
518582
<button className="btn btn-primary mt-3" onClick={saveFilters}>Apply Filters</button>

src/settings/settings_components/features.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const FeaturesList = () => {
55
const [loading, setLoading] = useState(true);
66

77
useEffect(() => {
8-
fetch(process.env.LAMBDA_URL)
8+
fetch(process.env.LAMBDA_URL + "features")
99
.then((res) => res.json())
1010
.then((json) => {
1111
setFeatures(json);

src/settings/settings_components/profile.jsx

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function Profile() {
1515
const [isSigningIn, setIsSigningIn] = useState(true);
1616
const [isLoggedIn, setIsLoggedIn] = useState(false);
1717
const [profileData, setProfileData] = useState(null);
18+
const [loadingSignIn, setLoadingSignIn] = useState(false);
1819

1920
const [loading, setLoading] = useState(true); // NEW
2021

@@ -56,6 +57,8 @@ export default function Profile() {
5657
// Sign Up handler
5758
function handleSignUp(e) {
5859
e.preventDefault();
60+
setLoadingSignIn(true);
61+
5962
const attributeList = [
6063
new CognitoUserAttribute({ Name: "email", Value: email }),
6164
new CognitoUserAttribute({ Name: "name", Value: name }),
@@ -68,6 +71,7 @@ export default function Profile() {
6871
console.log("Sign-up successful:", data);
6972
setNeedsConfirmation(true);
7073
}
74+
setLoadingSignIn(false);
7175
});
7276
}
7377

@@ -90,9 +94,40 @@ export default function Profile() {
9094
});
9195
}
9296

97+
async function fetchUserNotes(userId, idToken) {
98+
const res = await fetch(
99+
`${process.env.LAMBDA_URL}get_notes?userId=${userId}`,
100+
{
101+
method: "GET",
102+
headers: {
103+
Authorization: idToken,
104+
"Content-Type": "application/json",
105+
},
106+
}
107+
);
108+
109+
if (!res.ok) {
110+
throw new Error("Failed to fetch notes");
111+
}
112+
return await res.json();
113+
}
114+
115+
function mergeNotes(localNotes = [], cloudNotes = []) {
116+
const map = new Map();
117+
cloudNotes.forEach(note => {
118+
if (note.id) map.set(note.id, note);
119+
});
120+
localNotes.forEach(note => {
121+
if (note.id) map.set(note.id, note);
122+
});
123+
124+
return Array.from(map.values());
125+
}
126+
93127
// Sign In handler
94128
function handleSignIn(e) {
95129
e.preventDefault();
130+
setLoadingSignIn(true);
96131

97132
const authDetails = new AuthenticationDetails({
98133
Username: email,
@@ -105,7 +140,9 @@ export default function Profile() {
105140
});
106141

107142
user.authenticateUser(authDetails, {
108-
onSuccess: (session) => {
143+
onSuccess: async (session) => {
144+
setLoadingSignIn(false);
145+
109146
const idToken = session.getIdToken().getJwtToken();
110147
const accessToken = session.getAccessToken().getJwtToken();
111148
const refreshToken = session.getRefreshToken().getToken();
@@ -114,6 +151,21 @@ export default function Profile() {
114151
{ idToken, accessToken, refreshToken },
115152
() => console.log("Tokens stored in chrome.storage.local")
116153
);
154+
const userId = session.getIdToken().payload.sub;
155+
156+
try {
157+
const cloudNotes = await fetchUserNotes(userId, idToken);
158+
chrome.storage.local.get(["notes"], (result) => {
159+
const localNotes = result.notes || [];
160+
const merged = mergeNotes(localNotes, cloudNotes);
161+
162+
chrome.storage.local.set({ notes: merged }, () =>
163+
console.log("Notes merged successfully")
164+
);
165+
});
166+
} catch (err) {
167+
console.error(err);
168+
}
117169

118170
user.getUserAttributes((err, attrs) => {
119171
if (!err && attrs) {
@@ -127,6 +179,7 @@ export default function Profile() {
127179
});
128180
},
129181
onFailure: (err) => {
182+
setLoadingSignIn(false);
130183
alert(err.message || "Sign-in failed");
131184
},
132185
});
@@ -138,8 +191,8 @@ export default function Profile() {
138191
if (user) {
139192
user.signOut();
140193
}
141-
chrome.storage.local.clear(() => {
142-
console.log("Tokens cleared");
194+
chrome.storage.local.remove(["idToken", "accessToken", "refreshToken"], () => {
195+
console.log("Auth tokens cleared, notes preserved");
143196
setIsLoggedIn(false);
144197
setIsSigningIn(true);
145198
});
@@ -178,6 +231,7 @@ export default function Profile() {
178231
required
179232
/>
180233
<br/>
234+
<br/>
181235
<button type="submit" className="btn btn-primary btn-sm rounded-pill px-3 text-white">Confirm</button>
182236
</form>
183237
);
@@ -220,7 +274,14 @@ export default function Profile() {
220274
required
221275
/>
222276
<br/>
223-
<button type="submit">Sign Up</button>
277+
278+
<button type="submit" disabled={loadingSignIn}>
279+
{loadingSignIn ? (
280+
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
281+
) : (
282+
"Sign Up"
283+
)}
284+
</button>
224285
</form>
225286
</div>
226287

@@ -245,7 +306,13 @@ export default function Profile() {
245306
required
246307
/>
247308
<br/>
248-
<button type="submit">Sign In</button>
309+
<button type="submit" disabled={loadingSignIn}>
310+
{loadingSignIn ? (
311+
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
312+
) : (
313+
"Sign In"
314+
)}
315+
</button>
249316
</form>
250317
</div>
251318

0 commit comments

Comments
 (0)