+page.svelte 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <script>
  2. import { onMount } from 'svelte';
  3. import { authentication } from '../store.js';
  4. import { goto } from '$app/navigation';
  5. import { fade } from 'svelte/transition';
  6. let isAuthenticated = false;
  7. let isLoading = true;
  8. let showPassword = false;
  9. let isDisabled = false;
  10. $: authentication.subscribe((value) => {
  11. isAuthenticated = !!value;
  12. });
  13. onMount(() => {
  14. try {
  15. const storedAuth = localStorage.getItem('authentication');
  16. if (storedAuth) {
  17. authentication.set(JSON.parse(storedAuth));
  18. goto('/portfolio');
  19. }
  20. } catch (error) {
  21. console.error('Error parsing stored auth:', error);
  22. } finally {
  23. isLoading = false;
  24. }
  25. });
  26. async function submit(event) {
  27. event.preventDefault();
  28. isDisabled = true;
  29. const formData = new FormData(event.target);
  30. const username = formData.get('username');
  31. const password = formData.get('password');
  32. if (!username || !password) {
  33. alert('Please enter both username and password.');
  34. return;
  35. }
  36. const data = {
  37. username: username,
  38. password: password
  39. };
  40. try {
  41. const response = await fetch(import.meta.env.VITE_AUTH_HOST + '/api/authenticate', {
  42. method: 'POST',
  43. headers: {
  44. 'Content-Type': 'application/json'
  45. },
  46. body: JSON.stringify(data)
  47. });
  48. if (response.ok) {
  49. const result = await response.json();
  50. authentication.set(result);
  51. localStorage.setItem('authentication', JSON.stringify(result));
  52. await goto('/portfolio');
  53. } else {
  54. const error = await response.json();
  55. console.error('Login failed:', error);
  56. alert('Login failed: ' + error.message);
  57. }
  58. } catch (err) {
  59. console.error('Error during login:', err);
  60. alert('An error occurred. Please try again.');
  61. } finally {
  62. isDisabled = false;
  63. }
  64. }
  65. function navigateToRegister() {
  66. window.location.href = '/register';
  67. }
  68. function togglePasswordVisibility() {
  69. showPassword = !showPassword;
  70. }
  71. </script>
  72. <svelte:head>
  73. <title>Login</title>
  74. <meta name="description" content="Login page" />
  75. </svelte:head>
  76. <div in:fade class="flex justify-center items-center min-h-[70vh] px-4">
  77. {#if isLoading}
  78. <p class="text-gray-500 dark:text-gray-400">Loading...</p>
  79. {:else if !isAuthenticated}
  80. <div class="w-full max-w-sm bg-white dark:bg-gray-900 shadow-md rounded-xl p-6 space-y-6">
  81. <form on:submit={submit} class="space-y-4">
  82. <!-- Username -->
  83. <input
  84. type="text"
  85. name="username"
  86. placeholder="Username"
  87. class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
  88. />
  89. <!-- Password -->
  90. <div class="relative">
  91. <input
  92. type={showPassword ? 'text' : 'password'}
  93. name="password"
  94. placeholder="Password"
  95. class="w-full px-4 py-2 pr-10 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
  96. />
  97. <!-- Visibility toggle -->
  98. <span
  99. on:click={togglePasswordVisibility}
  100. class="absolute inset-y-0 right-3 flex items-center text-gray-500 dark:text-gray-400 cursor-pointer"
  101. >
  102. {#if showPassword}
  103. <!-- Eye Off Icon -->
  104. <svg
  105. xmlns="http://www.w3.org/2000/svg"
  106. class="h-5 w-5"
  107. fill="none"
  108. viewBox="0 0 24 24"
  109. stroke="currentColor"
  110. >
  111. <path
  112. stroke-linecap="round"
  113. stroke-linejoin="round"
  114. stroke-width="2"
  115. d="M13.875 18.825A10.05 10.05 0 0112 19c-5.523 0-10-4.03-10-9s4.477-9 10-9a9.96 9.96 0 014.586 1.055M15 12a3 3 0 11-6 0 3 3 0 016 0z"
  116. />
  117. <path
  118. stroke-linecap="round"
  119. stroke-linejoin="round"
  120. stroke-width="2"
  121. d="M2.458 12C3.732 15.943 7.46 19 12 19c1.5 0 2.91-.358 4.158-.99M19.742 16.018a9.97 9.97 0 002.258-4.018M15 12a3 3 0 00-3-3m0 0a3 3 0 01-3 3"
  122. />
  123. </svg>
  124. {:else}
  125. <!-- Eye Icon -->
  126. <svg
  127. xmlns="http://www.w3.org/2000/svg"
  128. class="h-5 w-5"
  129. fill="none"
  130. viewBox="0 0 24 24"
  131. stroke="currentColor"
  132. >
  133. <path
  134. stroke-linecap="round"
  135. stroke-linejoin="round"
  136. stroke-width="2"
  137. d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
  138. />
  139. <path
  140. stroke-linecap="round"
  141. stroke-linejoin="round"
  142. stroke-width="2"
  143. d="M2.458 12C3.732 15.943 7.46 19 12 19c4.54 0 8.268-3.057 9.542-7-1.274-3.943-5.002-7-9.542-7S3.732 8.057 2.458 12z"
  144. />
  145. </svg>
  146. {/if}
  147. </span>
  148. </div>
  149. <!-- Buttons -->
  150. <button
  151. type="submit"
  152. class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 rounded-lg transition flex justify-center items-center gap-2"
  153. disabled={isDisabled}
  154. >
  155. {#if isDisabled}
  156. <svg
  157. class="animate-spin h-5 w-5 text-white"
  158. xmlns="http://www.w3.org/2000/svg"
  159. fill="none"
  160. viewBox="0 0 24 24"
  161. >
  162. <circle
  163. class="opacity-25"
  164. cx="12"
  165. cy="12"
  166. r="10"
  167. stroke="currentColor"
  168. stroke-width="4"
  169. ></circle>
  170. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
  171. </svg>
  172. <span>Logging in...</span>
  173. {:else}
  174. <span>Login</span>
  175. {/if}
  176. </button>
  177. <button
  178. type="button"
  179. class="w-full bg-gray-300 hover:bg-gray-400 text-gray-800 py-2 rounded-lg transition"
  180. on:click={navigateToRegister}
  181. disabled={isDisabled}
  182. >
  183. Register
  184. </button>
  185. </form>
  186. </div>
  187. {/if}
  188. </div>