frontend.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. //static/js/frontend.js
  2. function setLoggedIn(loggedInBoolean) {
  3. let body = document.body;
  4. body.dataset.loggedIn = !!loggedInBoolean;
  5. }
  6. function createExistingVote(voter, vote) {
  7. let voteDiv = document.createElement('div');
  8. voteDiv.innerHTML = "";
  9. if (vote === "yes") {
  10. voteDiv.classList.add('vote-yes');
  11. } else if (vote === "no") {
  12. voteDiv.classList.add('vote-no');
  13. }
  14. voteDiv.innerText = voter;
  15. return voteDiv;
  16. }
  17. function createOneDayCard(dayData, currentSession, weather) {
  18. let date = new Date(dayData.date + 'T00:00:00');
  19. let card = document.createElement('div');
  20. card.innerHTML = `
  21. <div class="cardtop">
  22. <div class="date">
  23. <div class="dow">${date.toLocaleString("en-CA", { weekday: 'long' })}</div>
  24. <div class="dom">${date.toLocaleString("en-CA", { month: 'short', day: 'numeric' })}</div>
  25. </div>
  26. <div class="weather">
  27. <div class="temp">
  28. ?
  29. </div>
  30. <div class="weath">
  31. <img>
  32. </div>
  33. </div>
  34. </div>
  35. <div class="make-vote">
  36. <div class="satis-tier forloggedin">
  37. Can you attend?
  38. <div class="">
  39. <button class="vote yes" data-vote="yes">
  40. Yes ✔️
  41. </button>
  42. <button class="vote maybe" data-vote="">
  43. ??
  44. </button>
  45. <button class="vote no" data-vote="no">
  46. No ❌
  47. </button>
  48. </div>
  49. </div>
  50. </div>
  51. <div class="existing-votes">
  52. </div>
  53. `
  54. card.classList.add("card")
  55. card.dataset.date = dayData.date;
  56. if (weather) {
  57. // you need to figure this part out yourself
  58. }
  59. let existingVotesDiv = card.querySelector('.existing-votes');
  60. for (let [voter, vote] of Object.entries(dayData.votes)) {
  61. existingVotesDiv.append(createExistingVote(voter, vote))
  62. }
  63. return card;
  64. }
  65. function updateVotableDays(daysWithVotes, currentSession, weatherForecasts) {
  66. let daysView = document.querySelector(".days-view");
  67. if (!daysView) {
  68. console.error("could not find element to put days into")
  69. return;
  70. }
  71. daysView.innerHTML = '';
  72. for (let date in daysWithVotes) {
  73. let votes = daysWithVotes[date];
  74. let weather = weatherForecasts?.[date];
  75. daysView.append(createOneDayCard({
  76. date,
  77. votes
  78. }, currentSession, weather))
  79. }
  80. }
  81. class FrontendState {
  82. constructor() {
  83. this.currentSession = undefined;
  84. this.daysWithVotes = [];
  85. this.weatherForecasts = {};
  86. }
  87. async refreshAllState(updateView = true) {
  88. await Promise.all([
  89. this.refreshVotesState(false),
  90. // this.refreshWeatherState(false),
  91. this.refreshSessionState(false),
  92. ])
  93. if (updateView) {
  94. this.updateView();
  95. }
  96. }
  97. async refreshVotesState(updateView = true) {
  98. let {
  99. success,
  100. data,
  101. error
  102. } = await getVotesFromBackend()
  103. if (success) {
  104. this.daysWithVotes = data;
  105. } else {
  106. // ha ha I'm being lazy. can you do better?
  107. alert(error)
  108. }
  109. if (updateView) {
  110. this.updateView();
  111. }
  112. }
  113. async refreshWeatherState(updateView = true) {
  114. let {
  115. success,
  116. data,
  117. error
  118. } = await getWeather()
  119. if (success) {
  120. this.weatherForecasts = data;
  121. } else {
  122. // ha ha I'm being lazy. can you do better?
  123. alert(error)
  124. }
  125. if (updateView) {
  126. this.updateView();
  127. }
  128. }
  129. async refreshSessionState(updateView = true) {
  130. let {
  131. success,
  132. data,
  133. error
  134. } = await getSessionFromBackend()
  135. console.log(data)
  136. if (success) {
  137. if (data) { // 檢查 data 是否存在
  138. this.currentSession = data;
  139. setLoggedIn(true);
  140. updateUsernameDisplay(data.username); // 確保在 data 存在時才讀取 username
  141. } else {
  142. this.currentSession = undefined;
  143. setLoggedIn(false);
  144. updateUsernameDisplay(''); // 如果沒有用戶登入,清空用戶名顯示
  145. }
  146. } else {
  147. // 處理錯誤情況
  148. alert('Unable to refresh session state. Please try again later.');
  149. }
  150. if (updateView) {
  151. this.updateView();
  152. }
  153. }
  154. async updateView() {
  155. // 1. update the whole frontend so that it shows relevant logged-in vs logged-out features
  156. setLoggedIn(!!this.currentSession)
  157. // 2. optionally update the header so that it shows the username of the person logged in
  158. // updateHeader(this.currentSession)
  159. // 3. render the days
  160. updateVotableDays(this.daysWithVotes, this.currentSession, this.weatherForecasts)
  161. }
  162. }
  163. const fes = new FrontendState();
  164. fes.refreshAllState()
  165. async function handleAuthEvent(event) {
  166. event.preventDefault();
  167. // console.log(event.currentTarget, event.target)
  168. let usernameInput = event.currentTarget.querySelector('#headerusername');
  169. let usernameValue = usernameInput.value;
  170. let passwordInput = event.currentTarget.querySelector('#headerpassword');
  171. let passwordValue = passwordInput.value;
  172. let button = event.target.closest('button');
  173. if (button) {
  174. let authActionName = button?.dataset?.authAction;
  175. let authActionFunction = {
  176. signup: ajaxSignup,
  177. login: ajaxLogin,
  178. logout: ajaxLogout,
  179. } [authActionName];
  180. if (authActionFunction) {
  181. let authResult = await authActionFunction(usernameValue, passwordValue);
  182. if (authResult && authResult.success) {
  183. if (authActionName === 'logout') {
  184. // 登出成功,清除用戶名顯示
  185. updateUsernameDisplay('');
  186. } else {
  187. // 登入或註冊成功,更新用戶名顯示
  188. updateUsernameDisplay(authResult.data.username);
  189. }
  190. await fes.refreshSessionState();
  191. usernameInput.value = passwordInput.value = '';
  192. } else if (authResult) {
  193. // 處理登入、註冊或登出失敗的情況
  194. alert(authResult.error);
  195. } else {
  196. // 處理未知網絡錯誤
  197. alert("unknown network error");
  198. }
  199. }
  200. }
  201. }
  202. function updateUsernameDisplay(username) {
  203. const usernameSpan = document.querySelector('.username');
  204. const loggedOutDivs = document.querySelectorAll('.forloggedout');
  205. const loggedInDivs = document.querySelectorAll('.forloggedin');
  206. if (username) {
  207. usernameSpan.textContent = username;
  208. loggedOutDivs.forEach(div => div.style.display = 'none');
  209. loggedInDivs.forEach(div => div.style.display = 'block');
  210. } else {
  211. usernameSpan.textContent = 'nobody';
  212. loggedOutDivs.forEach(div => div.style.display = 'block');
  213. loggedInDivs.forEach(div => div.style.display = 'none');
  214. }
  215. }
  216. const authform = document.querySelector('form.authform')
  217. authform.addEventListener("click", handleAuthEvent);
  218. async function handleVoteEvent(event) {
  219. event.preventDefault();
  220. let button = event.target.closest('button.vote');
  221. // console.log(button)
  222. if (button) {
  223. let voteVal;
  224. if (button.classList.contains('yes')) {
  225. voteVal = 'yes';
  226. }
  227. if (button.classList.contains('no')) {
  228. voteVal = 'no';
  229. }
  230. if (button.classList.contains('maybe')) {
  231. voteVal = 'maybe';
  232. }
  233. let cardDiv = button.closest("div.card");
  234. if (!voteVal || !cardDiv) {
  235. // console.log({ voteVal, cardDiv })
  236. return;
  237. }
  238. let cardDate = cardDiv.dataset.date
  239. let voteActionResult = await setMyVote(cardDate, voteVal)
  240. if (voteActionResult) {
  241. await fes.refreshVotesState();
  242. }
  243. }
  244. }
  245. const daysViewDiv = document.querySelector('section.days-view');
  246. daysViewDiv.addEventListener("click", handleVoteEvent);