frontend.js 6.9 KB

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