* [PATCH gwmail 0/7] User Profile Page
@ 2025-02-27 23:22 Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 1/7] settings/account: Add header 'change password' Ammar Faizi
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
This series adds user profile page and photo profile support.
API examples can be found at:
https://github.com/GNUWeeb/mail.gnuweeb.org/blob/rework/old/profile.html
https://github.com/GNUWeeb/mail.gnuweeb.org/blob/rework/old/assets/js/api.js
You can also access it live at:
https://mail.gnuweeb.org/old/
Note: The login session is shared with the new interface. Both, old
and new interface use the same localStorage key.
There is one breaking change in the API, but it is very simple to
adapt. I also have updated the svelte code to keep up with the new
API. See patch #6 for details.
I will keep maintaining the old interface to let you know how to
use the API. It's easier that way for me rather than writing a
documentation.
Sync your git tree with master branch before start continuing the
profile page.
Signed-off-by: Ammar Faizi <[email protected]>
--
The following changes since commit 3a19417eed1530408c6a284c8747937657b27469:
feat: add settings pages (2025-02-23 15:31:17 +0700)
are available in the Git repository at:
https://github.com/GNUWeeb/mail.gnuweeb.org master
for you to fetch changes up to 0f781e613d2bc0f05ff64b1233073ad51cfd939f:
old: Use relative path to redirect (2025-02-28 05:29:15 +0700)
----------------------------------------------------------------
Ammar Faizi (8):
Merge branch 'patches-from-muhammad-rizki' into rework (patches from Muhammad Rizki)
settings/account: Add header 'change password'
public: Refactor old interface to keep up with new API
Rename 'public' to 'old'
old: Add profile page
old: Add photo profile support
routes: layout: Adjust field with new API
old: Use relative path to redirect
{public => old}/assets/css/.gitkeep | 0
old/assets/default_profile.png | Bin 0 -> 18016 bytes
old/assets/js/api.js | 239 ++++++++++++++++++++++++++++++++++++++++
{public => old}/home.html | 47 ++++----
{public => old}/index.html | 20 ++--
old/profile.html | 197 +++++++++++++++++++++++++++++++++
public/assets/js/api.js | 140 -----------------------
src/routes/(protected)/+layout.ts | 2 +-
src/routes/(protected)/settings/account/+page.svelte | 1 +
9 files changed, 478 insertions(+), 168 deletions(-)
rename {public => old}/assets/css/.gitkeep (100%)
create mode 100644 old/assets/default_profile.png
create mode 100644 old/assets/js/api.js
rename {public => old}/home.html (74%)
rename {public => old}/index.html (83%)
create mode 100644 old/profile.html
delete mode 100644 public/assets/js/api.js
--
Ammar Faizi
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH gwmail 1/7] settings/account: Add header 'change password'
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 2/7] public: Refactor old interface to keep up with new API Ammar Faizi
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
At a glance it's unclear that the account page is a "change password"
feature.
Signed-off-by: Ammar Faizi <[email protected]>
---
src/routes/(protected)/settings/account/+page.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/routes/(protected)/settings/account/+page.svelte b/src/routes/(protected)/settings/account/+page.svelte
index 00590bf9334d..99cfb7776533 100644
--- a/src/routes/(protected)/settings/account/+page.svelte
+++ b/src/routes/(protected)/settings/account/+page.svelte
@@ -49,6 +49,7 @@
);
</script>
+<h1>Change Password</h1><hr/>
<form use:enhance class="space-y-5">
<Form.Field {form} name="cur_pass">
<Form.Control>
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH gwmail 2/7] public: Refactor old interface to keep up with new API
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 1/7] settings/account: Add header 'change password' Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 3/7] Rename 'public' to 'old' Ammar Faizi
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
A preparation work to keep up with new API. This should be a good
example for the frontend developer to start working on new features.
Give only basic functionalities, I don't touch CSS much, I will let
Muhammad Rizki handle it.
Signed-off-by: Ammar Faizi <[email protected]>
---
public/assets/js/api.js | 168 +++++++++++++++++++++++++---------------
public/home.html | 53 ++++++++-----
public/index.html | 20 +++--
3 files changed, 151 insertions(+), 90 deletions(-)
diff --git a/public/assets/js/api.js b/public/assets/js/api.js
index 9a9cc7ef5af6..6593198aab2a 100644
--- a/public/assets/js/api.js
+++ b/public/assets/js/api.js
@@ -1,57 +1,83 @@
-
-const GWM_API_URL = "https://mail.gnuweeb.org/api.php?action=";
+const GWM_API_URL = "https://mail.gnuweeb.org/api2.php?action=";
const LS = localStorage;
+function gid(i)
+{
+ return document.getElementById(i);
+}
+
+function escape_html(s)
+{
+ return s.replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">");
+}
+
+function gwm_auth_get_token()
+{
+ return LS.getItem("gwm_token");
+}
+
function gwm_exec_api(p)
{
let xhr = new XMLHttpRequest();
xhr.open(p.method, p.url);
- xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.withCredentials = true;
+
+ if (p.ct === "json")
+ xhr.setRequestHeader("Content-Type", "application/json");
if (p.token)
xhr.setRequestHeader("Authorization", "Bearer " + p.token);
xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- if (p.callback)
- p.callback(JSON.parse(xhr.responseText));
+ let res;
+
+ if (xhr.readyState !== 4)
+ return;
+
+ try {
+ res = JSON.parse(xhr.responseText);
+ } catch (e) {
+ res = xhr.responseText;
}
- }
- xhr.send(JSON.stringify(p.data));
+
+ if (p.callback)
+ p.callback(res, xhr);
+ };
+ xhr.send(p.data);
}
-function gwm_api_get_user_info(tkn, cbk)
+function gwm_exec_api_multipart(p)
{
- gwm_exec_api({
- method: "GET",
- url: GWM_API_URL + "get_user_info",
- token: tkn,
- callback: cbk
- });
+ p.ct = "multipart";
+ gwm_exec_api(p);
+}
+
+function gwm_exec_api_json(p)
+{
+ p.ct = "json";
+ p.data = JSON.stringify(p.data);
+ gwm_exec_api(p);
}
-function gwm_api_login(user, pass, cbk)
+function gwm_api_get_user_info(cb)
{
gwm_exec_api({
- method: "POST",
- url: GWM_API_URL + "login",
- data: { user: user, pass: pass },
- callback: cbk
+ method: "GET",
+ url: GWM_API_URL + "get_user_info&renew_token=1",
+ token: LS.getItem("gwm_token"),
+ callback: cb
});
}
-function gwm_api_change_password(tkn, cur_pass, new_pass, cbk)
+function gwm_api_login(cb, user, pass)
{
- gwm_exec_api({
+ gwm_exec_api_json({
method: "POST",
- url: GWM_API_URL + "change_password",
- token: tkn,
- data: {
- cur_pass: cur_pass,
- new_pass: new_pass,
- retype_new_pass: new_pass
- },
- callback: cbk
+ url: GWM_API_URL + "login",
+ data: { user: user, pass: pass },
+ callback: cb
});
}
@@ -70,71 +96,87 @@ function gwm_cb_login(j)
window.location.href = "/home.html";
}
-function gwm_fn_login(user, pass)
+function gwm_fn_login(cb, user, pass)
{
- gwm_api_login(user, pass, gwm_cb_login);
+ gwm_api_login(cb, user, pass);
}
-function gwm_cb_change_pass(j)
+function gwm_cb_change_password(j)
{
- if (j.code === 200) {
- alert("Password changed successfully!");
- window.location.href = "?";
- } else {
- alert("Failed to change password: " + JSON.stringify(j.res));
+ if (j.code !== 200) {
+ alert("Password change failed: " + JSON.stringify(j.res));
+ return false;
}
-}
-function gwm_fn_change_pass(cur_pass, new_pass)
-{
- let tkn = LS.getItem("gwm_token");
- gwm_api_change_password(tkn, cur_pass, new_pass, gwm_cb_change_pass);
+ alert("Password changed successfully!");
+ return true;
}
-function gwm_redirect_if_authorized()
+function gwm_fn_change_password(cb, cur_pass, new_pass, retype_new_pass)
{
- if (LS.getItem("gwm_token"))
- window.location.href = "/home.html";
+ gwm_exec_api_json({
+ method: "POST",
+ url: GWM_API_URL + "change_password",
+ data: {
+ cur_pass: cur_pass,
+ new_pass: new_pass,
+ retype_new_pass: retype_new_pass
+ },
+ callback: cb,
+ token: gwm_auth_get_token()
+ });
}
-function gwm_do_logout()
+function gwm_fn_logout()
{
LS.clear();
window.location.href = "/";
}
-function gwm_gu_cb(j)
+function gwm_auth_get_user()
{
- if (j.code === 200) {
- LS.setItem("gwm_uinfo", JSON.stringify(j.res));
- } else {
- alert("Your session has expired. Please login again.");
- gwm_do_logout();
+ return JSON.parse(LS.getItem("gwm_uinfo"));
+}
+
+function gwm_auth_redirect_if_authorized()
+{
+ if (LS.getItem("gwm_token")) {
+ window.location.href = "/home.html";
+ return true;
}
+
+ return false;
}
-function gwm_redirect_if_not_authorized()
+function gwm_auth_redirect_if_not_authorized()
{
let tkn = LS.getItem("gwm_token");
let uio = LS.getItem("gwm_uinfo");
let tkn_exp_at = LS.getItem("gwm_token_exp_at");
if (!tkn || !uio) {
- gwm_do_logout();
- return;
+ gwm_fn_logout();
+ return true;
}
let unix = Math.round((new Date()).getTime() / 1000);
if (unix >= tkn_exp_at) {
alert("Your session has expired. Please login again.");
- gwm_do_logout();
- return;
+ gwm_fn_logout();
+ return true;
}
- gwm_api_get_user_info(tkn, gwm_gu_cb);
-}
+ gwm_api_get_user_info(function(j) {
+ if (j.code !== 200) {
+ alert("Your session has expired. Please login again.");
+ gwm_fn_logout();
+ return;
+ }
-function gwm_get_user_info()
-{
- return JSON.parse(LS.getItem("gwm_uinfo"));
+ let rt = j.res.renew_token;
+ LS.setItem("gwm_uinfo", JSON.stringify(j.res.user_info));
+ LS.setItem("gwm_token", rt.token);
+ LS.setItem("gwm_token_exp_at", rt.token_exp_at);
+ });
+ return false;
}
diff --git a/public/home.html b/public/home.html
index 1eb61c2d369f..baaf067c8f20 100644
--- a/public/home.html
+++ b/public/home.html
@@ -37,7 +37,7 @@ body {
</head>
<body>
<div class="main-cage">
- <a href="javascript:gwm_do_logout();">
+ <a href="javascript:gwm_fn_logout();">
<button class="cg-btn">Logout</button>
</a>
<h1>GNU/Weeb Mail Dashboard</h1>
@@ -50,7 +50,7 @@ body {
<tbody>
<tr><td>Current Password</td><td>:</td><td><input type="password" name="cur_pass" required/></td></tr>
<tr><td>New Password</td><td>:</td><td><input type="password" name="new_pass" required/></td></tr>
- <tr><td>Retype New Password</td><td>:</td><td><input type="password" name="retyped_new_pass" required/></td></tr>
+ <tr><td>Retype New Password</td><td>:</td><td><input type="password" name="retype_new_pass" required/></td></tr>
<tr><td colspan="3" align="center"><button type="submit" class="cg-btn">Change Password</button></td></tr>
</tbody>
</table>
@@ -80,23 +80,38 @@ Auth: Normal Password
</div>
</body>
<script>
-gwm_redirect_if_not_authorized();
-let u = gwm_get_user_info();
-function gid(id) { return document.getElementById(id); }
-function escape_html(s) { return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); }
-gid("uinfo_full_name").innerHTML = escape_html(u.full_name);
-let fr = gid("change_pass_form");
-fr.onsubmit = function() {
- let cur_pass = fr.cur_pass.value;
- let new_pass = fr.new_pass.value;
- let retyped_new_pass = fr.retyped_new_pass.value;
- if (new_pass !== retyped_new_pass) {
- alert("New password and retyped new password does not match!");
- return false;
- }
-
- gwm_fn_change_pass(cur_pass, new_pass);
-};
+function toggle_disable_inputs(form, disable)
+{
+ for (let i = 0; i < form.length; i++)
+ form[i].disabled = disable;
+}
+
+function main()
+{
+ let u, cpf;
+
+ if (gwm_auth_redirect_if_not_authorized())
+ return;
+
+ u = gwm_auth_get_user();
+ gid("uinfo_full_name").innerText = escape_html(u.full_name);
+ cpf = gid("change_pass_form");
+ cpf.onsubmit = function(e) {
+ e.preventDefault();
+
+ toggle_disable_inputs(cpf, true);
+ let cb = function (j) {
+ toggle_disable_inputs(cpf, false);
+ if (gwm_cb_change_password(j))
+ cpf.reset();
+ };
+
+ gwm_fn_change_password(cb, cpf.cur_pass.value,
+ cpf.new_pass.value,
+ cpf.retype_new_pass.value);
+ };
+}
+main();
</script>
</html>
diff --git a/public/index.html b/public/index.html
index ed300eba0964..b46da5787ea4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -41,13 +41,17 @@
</div>
</body>
<script>
-gwm_redirect_if_authorized();
-let fr = document.getElementById("login-form");
-fr.onsubmit = function(e) {
- e.preventDefault();
- let user = fr.user.value;
- let pass = fr.pass.value;
- gwm_fn_login(user, pass);
-};
+function main()
+{
+ if (gwm_auth_redirect_if_authorized())
+ return;
+
+ let fr = gid("login-form");
+ fr.onsubmit = function(e) {
+ e.preventDefault();
+ gwm_fn_login(gwm_cb_login, fr.user.value, fr.pass.value);
+ };
+}
+main();
</script>
</html>
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH gwmail 3/7] Rename 'public' to 'old'
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 1/7] settings/account: Add header 'change password' Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 2/7] public: Refactor old interface to keep up with new API Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 4/7] old: Add profile page Ammar Faizi
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
Keep the old interface in old directory. We will also public `/old/`
URL on the main site. Just in case.
Signed-off-by: Ammar Faizi <[email protected]>
---
{public => old}/assets/css/.gitkeep | 0
{public => old}/assets/js/api.js | 0
{public => old}/home.html | 0
{public => old}/index.html | 0
4 files changed, 0 insertions(+), 0 deletions(-)
rename {public => old}/assets/css/.gitkeep (100%)
rename {public => old}/assets/js/api.js (100%)
rename {public => old}/home.html (100%)
rename {public => old}/index.html (100%)
diff --git a/public/assets/css/.gitkeep b/old/assets/css/.gitkeep
similarity index 100%
rename from public/assets/css/.gitkeep
rename to old/assets/css/.gitkeep
diff --git a/public/assets/js/api.js b/old/assets/js/api.js
similarity index 100%
rename from public/assets/js/api.js
rename to old/assets/js/api.js
diff --git a/public/home.html b/old/home.html
similarity index 100%
rename from public/home.html
rename to old/home.html
diff --git a/public/index.html b/old/index.html
similarity index 100%
rename from public/index.html
rename to old/index.html
--
Ammar Faizi
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH gwmail 4/7] old: Add profile page
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
` (2 preceding siblings ...)
2025-02-27 23:22 ` [PATCH gwmail 3/7] Rename 'public' to 'old' Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 5/7] old: Add photo profile support Ammar Faizi
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
Initial example of profile related API.
Signed-off-by: Ammar Faizi <[email protected]>
---
old/assets/js/api.js | 58 ++++++++++++----
old/home.html | 8 +--
old/profile.html | 159 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 206 insertions(+), 19 deletions(-)
create mode 100644 old/profile.html
diff --git a/old/assets/js/api.js b/old/assets/js/api.js
index 6593198aab2a..9a14a83633c8 100644
--- a/old/assets/js/api.js
+++ b/old/assets/js/api.js
@@ -6,6 +6,12 @@ function gid(i)
return document.getElementById(i);
}
+function toggle_disable_inputs(form, disable)
+{
+ for (let i = 0; i < form.length; i++)
+ form[i].disabled = disable;
+}
+
function escape_html(s)
{
return s.replace(/&/g, "&")
@@ -71,6 +77,17 @@ function gwm_api_get_user_info(cb)
});
}
+function gwm_api_set_user_info(cb, data)
+{
+ gwm_exec_api_multipart({
+ method: "POST",
+ url: GWM_API_URL + "set_user_info",
+ data: data,
+ token: LS.getItem("gwm_token"),
+ callback: cb
+ });
+}
+
function gwm_api_login(cb, user, pass)
{
gwm_exec_api_json({
@@ -127,6 +144,11 @@ function gwm_fn_change_password(cb, cur_pass, new_pass, retype_new_pass)
});
}
+function gwm_fn_set_user_info(cb, data)
+{
+ gwm_api_set_user_info(cb, data);
+}
+
function gwm_fn_logout()
{
LS.clear();
@@ -148,6 +170,29 @@ function gwm_auth_redirect_if_authorized()
return false;
}
+function gwm_auth_renew_session(cb = null)
+{
+ gwm_api_get_user_info(function(j, x) {
+ // If cancelled, do nothing
+ if (x.status === 0)
+ return;
+
+ if (j.code !== 200) {
+ alert("Your session has expired. Please login again.");
+ gwm_fn_logout();
+ return;
+ }
+
+ let rt = j.res.renew_token;
+ LS.setItem("gwm_uinfo", JSON.stringify(j.res.user_info));
+ LS.setItem("gwm_token", rt.token);
+ LS.setItem("gwm_token_exp_at", rt.token_exp_at);
+
+ if (cb)
+ cb();
+ });
+}
+
function gwm_auth_redirect_if_not_authorized()
{
let tkn = LS.getItem("gwm_token");
@@ -166,17 +211,6 @@ function gwm_auth_redirect_if_not_authorized()
return true;
}
- gwm_api_get_user_info(function(j) {
- if (j.code !== 200) {
- alert("Your session has expired. Please login again.");
- gwm_fn_logout();
- return;
- }
-
- let rt = j.res.renew_token;
- LS.setItem("gwm_uinfo", JSON.stringify(j.res.user_info));
- LS.setItem("gwm_token", rt.token);
- LS.setItem("gwm_token_exp_at", rt.token_exp_at);
- });
+ gwm_auth_renew_session();
return false;
}
diff --git a/old/home.html b/old/home.html
index baaf067c8f20..4ba0039d044b 100644
--- a/old/home.html
+++ b/old/home.html
@@ -43,6 +43,7 @@ body {
<h1>GNU/Weeb Mail Dashboard</h1>
<h2>Welcome <span id="uinfo_full_name"></span></h2>
<h2>This is an emergency page to change your password. It's still under development.</h2>
+ <h1><a href="profile.html">Open Profile</a></h1>
<div class="form-cage">
<form action="javascript:void(0);" method="POST" id="change_pass_form">
<h1>Change Password</h1>
@@ -80,13 +81,6 @@ Auth: Normal Password
</div>
</body>
<script>
-
-function toggle_disable_inputs(form, disable)
-{
- for (let i = 0; i < form.length; i++)
- form[i].disabled = disable;
-}
-
function main()
{
let u, cpf;
diff --git a/old/profile.html b/old/profile.html
new file mode 100644
index 000000000000..07fa8f8434a0
--- /dev/null
+++ b/old/profile.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>GNU/Weeb Mail Login</title>
+<meta charset="UTF-8"/>
+<meta name="viewport" content="width=device-width,initial-scale=1.00"/>
+<script type="text/javascript" src="assets/js/api.js"></script>
+</head>
+<body>
+<center><a href="/home.html"><h1>Back to Home</h1></a></center>
+<div id="frcg">
+ <form method="POST" id="uform" enctype="application/x-www-form-urlencoded">
+ <table>
+ <thead>
+ <tr colspan="3"><td></td></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td align="center" colspan="3">
+ <div id="photo_box"></div>
+ </td>
+ </tr>
+ <tr>
+ <td>Upload Photo</td>
+ <td>:</td>
+ <td><input type="file" name="photo" id="uform_photo"/></td>
+ </tr>
+ <tr>
+ <td>Full Name</td>
+ <td>:</td>
+ <td><input type="text" name="full_name" id="uform_full_name" required/></td>
+ </tr>
+ <tr>
+ <td>External Email</td>
+ <td>:</td>
+ <td><input type="email" name="ext_email" id="uform_ext_email" required/></td>
+ </tr>
+ <tr>
+ <td>Gender</td>
+ <td>:</td>
+ <td>
+ <select name="gender" id="uform_gender" required>
+ <option value=""></option>
+ <option value="m">Male</option>
+ <option value="f">Female</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <!-- Username cannot be changed, so disable the input. -->
+ <td>Username</td>
+ <td>:</td>
+ <td><input type="text" name="username" disabled="1" id="uform_username"/></td>
+ </tr>
+ <tr>
+ <td>GitHub Username</td>
+ <td>:</td>
+ <td><input type="text" name="socials[github_username]" id="uform_github_username" required/></td>
+ </tr>
+ <tr>
+ <td>Telegram Username</td>
+ <td>:</td>
+ <td><input type="text" name="socials[telegram_username]" id="uform_telegram_username" required/></td>
+ </tr>
+ <tr>
+ <td>Twitter Username</td>
+ <td>:</td>
+ <td><input type="text" name="socials[twitter_username]" id="uform_twitter_username" required/></td>
+ </tr>
+ <tr>
+ <td>Discord Username</td>
+ <td>:</td>
+ <td><input type="text" name="socials[discord_username]" id="uform_discord_username" required/></td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr><td colspan="3"><hr/></td></tr>
+ <tr>
+ <td>Enter your password</td>
+ <td>:</td>
+ <td><input type="password" name="password" required/></td>
+ </tr>
+ <tr><td colspan="3"><hr/></td></tr>
+ <tr>
+ <td align="center" colspan="3">
+ <button type="submit">Save</button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </form>
+</div>
+<style>
+#photo_box {
+ width: 200px;
+ height: 200px;
+ border: 1px solid #000;
+}
+#frcg {
+ margin: 0 auto;
+ margin-top: 100px;
+}
+#uform {
+ padding: 20px;
+ margin: 0 auto;
+ width: 800px;
+ border: 1px solid #000;
+}
+#uform table {
+ padding: 20px;
+ margin: 0 auto;
+ width: 500px;
+ border: 1px solid #000;
+}
+</style>
+<script>
+function form_uinfo_set_inputs(u)
+{
+ gid("uform_full_name").value = u.full_name;
+ gid("uform_ext_email").value = u.ext_email;
+ gid("uform_gender").value = u.gender;
+ gid("uform_username").value = u.username;
+ gid("uform_github_username").value = u.socials.github_username;
+ gid("uform_telegram_username").value = u.socials.telegram_username;
+ gid("uform_twitter_username").value = u.socials.twitter_username;
+ gid("uform_discord_username").value = u.socials.discord_username;
+}
+
+function main()
+{
+ if (gwm_auth_redirect_if_not_authorized())
+ return;
+
+ let u = gwm_auth_get_user();
+ if (!u) {
+ gwm_fn_logout();
+ return;
+ }
+
+ form_uinfo_set_inputs(u);
+ let photo = gid("uform_photo");
+ let uform = gid("uform");
+ uform.addEventListener("submit", function(e) {
+ e.preventDefault();
+ let fd = new FormData(uform);
+ gwm_fn_set_user_info(function (j) {
+ gwm_auth_renew_session(function () {
+ toggle_disable_inputs(uform, false);
+ let u = gwm_auth_get_user();
+ form_uinfo_set_inputs(u);
+ });
+ }, fd);
+ toggle_disable_inputs(uform, true);
+ });
+}
+main();
+</script>
+</body>
+</html>
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH gwmail 5/7] old: Add photo profile support
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
` (3 preceding siblings ...)
2025-02-27 23:22 ` [PATCH gwmail 4/7] old: Add profile page Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 6/7] routes: layout: Adjust field with new API Ammar Faizi
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
Initial photo profile support. The backend has already provided the API.
Implement the feature on the frontend as well.
Signed-off-by: Ammar Faizi <[email protected]>
---
old/assets/default_profile.png | Bin 0 -> 18016 bytes
old/assets/js/api.js | 25 ++++++++++++++++-
old/profile.html | 48 +++++++++++++++++++++++++++++----
3 files changed, 67 insertions(+), 6 deletions(-)
create mode 100644 old/assets/default_profile.png
diff --git a/old/assets/default_profile.png b/old/assets/default_profile.png
new file mode 100644
index 0000000000000000000000000000000000000000..70830c1d642dbf0e7945765730c6a5607764f0ad
GIT binary patch
literal 18016
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
L1+fYd!@&OoaMkP(
literal 0
HcmV?d00001
diff --git a/old/assets/js/api.js b/old/assets/js/api.js
index 9a14a83633c8..0a9109146c43 100644
--- a/old/assets/js/api.js
+++ b/old/assets/js/api.js
@@ -146,7 +146,12 @@ function gwm_fn_change_password(cb, cur_pass, new_pass, retype_new_pass)
function gwm_fn_set_user_info(cb, data)
{
- gwm_api_set_user_info(cb, data);
+ let callback = function (j) {
+ alert(j.res);
+ if (cb)
+ cb(j);
+ };
+ gwm_api_set_user_info(callback, data);
}
function gwm_fn_logout()
@@ -214,3 +219,21 @@ function gwm_auth_redirect_if_not_authorized()
gwm_auth_renew_session();
return false;
}
+
+function gwm_api_delete_user_photo(cb)
+{
+ gwm_exec_api({
+ method: "GET",
+ url: GWM_API_URL + "delete_user_photo",
+ token: LS.getItem("gwm_token"),
+ callback: cb
+ });
+}
+
+function gwm_fn_delete_user_photo(cb)
+{
+ if (!confirm("Are you sure you want to delete your photo?"))
+ return;
+
+ gwm_api_delete_user_photo(cb);
+}
diff --git a/old/profile.html b/old/profile.html
index 07fa8f8434a0..c2a3c53618cf 100644
--- a/old/profile.html
+++ b/old/profile.html
@@ -17,11 +17,14 @@
<tbody>
<tr>
<td align="center" colspan="3">
- <div id="photo_box"></div>
+ <div id="del_photo" style="margin-bottom: 20px;"><a href="javascript:void();">Delete Photo</a></div>
+ <div id="photo_box">
+ <img src="assets/default_profile.png" id="photo_obj">
+ </div>
</td>
</tr>
<tr>
- <td>Upload Photo</td>
+ <td>Upload New Photo</td>
<td>:</td>
<td><input type="file" name="photo" id="uform_photo"/></td>
</tr>
@@ -95,6 +98,13 @@
width: 200px;
height: 200px;
border: 1px solid #000;
+ border-radius: 100%;
+ margin-bottom: 50px;
+}
+#photo_obj {
+ width: 100%;
+ height: 100%;
+ border-radius: 100%;
}
#frcg {
margin: 0 auto;
@@ -114,6 +124,20 @@
}
</style>
<script>
+function delete_photo_click_callback()
+{
+ gwm_fn_delete_user_photo(function (j) {
+ gwm_auth_renew_session(function () {
+ form_uinfo_set_inputs(gwm_auth_get_user());
+ });
+
+ if (j.code === 200) {
+ gid("photo_obj").src = "assets/default_profile.png";
+ dp.style.display = "none";
+ }
+ });
+}
+
function form_uinfo_set_inputs(u)
{
gid("uform_full_name").value = u.full_name;
@@ -124,6 +148,19 @@ function form_uinfo_set_inputs(u)
gid("uform_telegram_username").value = u.socials.telegram_username;
gid("uform_twitter_username").value = u.socials.twitter_username;
gid("uform_discord_username").value = u.socials.discord_username;
+
+ if (u.photo)
+ gid("photo_obj").src = u.photo;
+
+ let dp = gid("del_photo");
+ if (u.photo) {
+ dp.style.display = "block";
+ dp.onclick = function() {
+ delete_photo_click_callback();
+ };
+ } else {
+ dp.style.display = "none";
+ }
}
function main()
@@ -144,11 +181,12 @@ function main()
e.preventDefault();
let fd = new FormData(uform);
gwm_fn_set_user_info(function (j) {
+ toggle_disable_inputs(uform, false);
gwm_auth_renew_session(function () {
- toggle_disable_inputs(uform, false);
- let u = gwm_auth_get_user();
- form_uinfo_set_inputs(u);
+ form_uinfo_set_inputs(gwm_auth_get_user());
});
+ if (j.code === 200)
+ photo.value = "";
}, fd);
toggle_disable_inputs(uform, true);
});
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH gwmail 6/7] routes: layout: Adjust field with new API
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
` (4 preceding siblings ...)
2025-02-27 23:22 ` [PATCH gwmail 5/7] old: Add photo profile support Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 7/7] old: Use relative path to redirect Ammar Faizi
2025-02-27 23:28 ` [PATCH gwmail 0/7] User Profile Page Ammar Faizi
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
A bit breaking change, all user info is now stored at "user_info" field.
Before:
```
{
"code": 200,
"res": {
"id": 2,
"full_name": "Ammar Faizi",
"gender": "m",
"username": "ammarfaizi2",
"role": "user",
"is_active": "1",
"renew_token": {
"token": "...",
"token_exp_at": 1741842452
}
}
}
```
After:
```
{
"code": 200,
"res": {
"user_info": {
"id": 2,
"full_name": "Ammar Faizi",
"gender": "m",
"username": "ammarfaizi2",
"ext_email": "[email protected]",
"role": "user",
"is_active": "1",
"socials": {
"github_username": "ammarfaizi2",
"telegram_username": "ammarfaizi2",
"twitter_username": "ammarfaizi2",
"discord_username": "ammarfaizi2"
},
"photo": null
},
"renew_token": {
"token": "...",
"token_exp_at": 1741842452
}
}
}
```
Signed-off-by: Ammar Faizi <[email protected]>
---
src/routes/(protected)/+layout.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/(protected)/+layout.ts b/src/routes/(protected)/+layout.ts
index 2d1813a2534e..64019ba23012 100644
--- a/src/routes/(protected)/+layout.ts
+++ b/src/routes/(protected)/+layout.ts
@@ -16,6 +16,6 @@ export const load: LayoutLoad = async () => {
params: { action: "get_user_info" }
});
- localStorage.setItem("gwm_uinfo", JSON.stringify(data.res));
+ localStorage.setItem("gwm_uinfo", JSON.stringify(data.res.user_info));
auth.refresh();
};
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH gwmail 7/7] old: Use relative path to redirect
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
` (5 preceding siblings ...)
2025-02-27 23:22 ` [PATCH gwmail 6/7] routes: layout: Adjust field with new API Ammar Faizi
@ 2025-02-27 23:22 ` Ammar Faizi
2025-02-27 23:28 ` [PATCH gwmail 0/7] User Profile Page Ammar Faizi
7 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:22 UTC (permalink / raw)
To: Muhammad Rizki
Cc: Ammar Faizi, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
Since we are now in a sub directory, use relative path.
Signed-off-by: Ammar Faizi <[email protected]>
---
old/assets/js/api.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/old/assets/js/api.js b/old/assets/js/api.js
index 0a9109146c43..eb1467cd4543 100644
--- a/old/assets/js/api.js
+++ b/old/assets/js/api.js
@@ -1,4 +1,4 @@
-const GWM_API_URL = "https://mail.gnuweeb.org/api2.php?action=";
+const GWM_API_URL = "https://mail.gnuweeb.org/api.php?action=";
const LS = localStorage;
function gid(i)
@@ -110,7 +110,7 @@ function gwm_cb_login(j)
LS.setItem("gwm_token_exp_at", r.token_exp_at);
LS.setItem("gwm_uinfo", JSON.stringify(r.user_info));
alert("Login successful!");
- window.location.href = "/home.html";
+ window.location.href = "home.html";
}
function gwm_fn_login(cb, user, pass)
@@ -157,7 +157,7 @@ function gwm_fn_set_user_info(cb, data)
function gwm_fn_logout()
{
LS.clear();
- window.location.href = "/";
+ window.location.href = "index.html";
}
function gwm_auth_get_user()
@@ -168,7 +168,7 @@ function gwm_auth_get_user()
function gwm_auth_redirect_if_authorized()
{
if (LS.getItem("gwm_token")) {
- window.location.href = "/home.html";
+ window.location.href = "home.html";
return true;
}
--
Ammar Faizi
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH gwmail 0/7] User Profile Page
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
` (6 preceding siblings ...)
2025-02-27 23:22 ` [PATCH gwmail 7/7] old: Use relative path to redirect Ammar Faizi
@ 2025-02-27 23:28 ` Ammar Faizi
2025-02-27 23:31 ` Ammar Faizi
7 siblings, 1 reply; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:28 UTC (permalink / raw)
To: Muhammad Rizki; +Cc: GNU/Weeb Mailing List, Alviro Iskandar Setiawan
[-- Attachment #1: Type: text/plain, Size: 153 bytes --]
On 2/28/25 6:22 AM, Ammar Faizi wrote:
> This series adds user profile page and photo profile support.
The basic page looks like this.
--
Ammar Faizi
[-- Attachment #2: Screenshot from 2025-02-28 06-27-27.png --]
[-- Type: image/png, Size: 168623 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH gwmail 0/7] User Profile Page
2025-02-27 23:28 ` [PATCH gwmail 0/7] User Profile Page Ammar Faizi
@ 2025-02-27 23:31 ` Ammar Faizi
0 siblings, 0 replies; 10+ messages in thread
From: Ammar Faizi @ 2025-02-27 23:31 UTC (permalink / raw)
To: Muhammad Rizki; +Cc: GNU/Weeb Mailing List, Alviro Iskandar Setiawan
On 2/28/25 6:28 AM, Ammar Faizi wrote:
> On 2/28/25 6:22 AM, Ammar Faizi wrote:
>> This series adds user profile page and photo profile support.
>
> The basic page looks like this.
I am not sure how to make the profile page resolution proportional.
Maybe we need a crop function to force the image to be square.
But that's not our priority. At least it works for now.
--
Ammar Faizi
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-02-27 23:31 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-27 23:22 [PATCH gwmail 0/7] User Profile Page Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 1/7] settings/account: Add header 'change password' Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 2/7] public: Refactor old interface to keep up with new API Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 3/7] Rename 'public' to 'old' Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 4/7] old: Add profile page Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 5/7] old: Add photo profile support Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 6/7] routes: layout: Adjust field with new API Ammar Faizi
2025-02-27 23:22 ` [PATCH gwmail 7/7] old: Use relative path to redirect Ammar Faizi
2025-02-27 23:28 ` [PATCH gwmail 0/7] User Profile Page Ammar Faizi
2025-02-27 23:31 ` Ammar Faizi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox