From 6c04e1b034c2c29192b54fa5b0084aa815c63c82 Mon Sep 17 00:00:00 2001
From: Sondre Malerud <sondrmal@stud.ntnu.no>
Date: Mon, 24 Apr 2023 13:27:09 +0200
Subject: [PATCH] Velg profil

---
 .env                                          |   1 +
 cypress/e2e/example.cy.js                     |   8 --
 cypress/e2e/login.cy.js                       |  16 +++
 package-lock.json                             |  15 +++
 package.json                                  |   2 +
 src/App.vue                                   |  13 --
 src/assets/base.css                           |  13 --
 src/components/icons/logo.png                 | Bin 0 -> 17006 bytes
 src/main.js                                   |   6 +-
 src/router/index.js                           |  16 ++-
 src/stores/authStore.js                       |  32 +++++
 src/stores/counter.js                         |  12 --
 src/util/API.js                               | 106 ++++++++++++++++
 src/views/AboutView.vue                       |  15 ---
 src/views/LoginView.vue                       | 114 ++++++++++++++++++
 src/views/SelectProfileView.vue               | 113 +++++++++++++++++
 src/views/__tests__/LoginView.spec.js         |  16 +++
 src/views/__tests__/SelectProfileView.spec.js |  28 +++++
 18 files changed, 458 insertions(+), 68 deletions(-)
 create mode 100644 .env
 delete mode 100644 cypress/e2e/example.cy.js
 create mode 100644 cypress/e2e/login.cy.js
 create mode 100644 src/components/icons/logo.png
 create mode 100644 src/stores/authStore.js
 delete mode 100644 src/stores/counter.js
 create mode 100644 src/util/API.js
 delete mode 100644 src/views/AboutView.vue
 create mode 100644 src/views/LoginView.vue
 create mode 100644 src/views/SelectProfileView.vue
 create mode 100644 src/views/__tests__/LoginView.spec.js
 create mode 100644 src/views/__tests__/SelectProfileView.spec.js

diff --git a/.env b/.env
new file mode 100644
index 0000000..d6e1cfd
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+VITE_BACKEND_URL = "http://localhost:8080"
\ No newline at end of file
diff --git a/cypress/e2e/example.cy.js b/cypress/e2e/example.cy.js
deleted file mode 100644
index 7a8c909..0000000
--- a/cypress/e2e/example.cy.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// https://docs.cypress.io/api/introduction/api.html
-
-describe('My First Test', () => {
-  it('visits the app root url', () => {
-    cy.visit('/')
-    cy.contains('h1', 'You did it!')
-  })
-})
diff --git a/cypress/e2e/login.cy.js b/cypress/e2e/login.cy.js
new file mode 100644
index 0000000..50b8b97
--- /dev/null
+++ b/cypress/e2e/login.cy.js
@@ -0,0 +1,16 @@
+describe('Login fails with wrong credentials', () => {
+  it('passes', () => {
+    cy.visit('http://localhost:4173/login')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+    
+    cy.get('#email-input').type('en bruker som ikke finnes')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+
+    cy.get('#password-input').type('hei')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+  })
+
+})
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 67f061c..c8c1d66 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,9 @@
       "name": "frontend",
       "version": "0.0.0",
       "dependencies": {
+        "jwt-decode": "^3.1.2",
         "pinia": "^2.0.28",
+        "pinia-plugin-persistedstate": "^3.1.0",
         "vue": "^3.2.45",
         "vue-router": "^4.1.6"
       },
@@ -2355,6 +2357,11 @@
         "verror": "1.10.0"
       }
     },
+    "node_modules/jwt-decode": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+      "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+    },
     "node_modules/lazy-ass": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@@ -2815,6 +2822,14 @@
         }
       }
     },
+    "node_modules/pinia-plugin-persistedstate": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.1.0.tgz",
+      "integrity": "sha512-8UN+vYMEPBdgNLwceY08mi5olI0wkYaEb8b6hD6xW7SnBRuPydWHlEhZvUWgNb/ibuf4PvufpvtS+dmhYjJQOw==",
+      "peerDependencies": {
+        "pinia": "^2.0.0"
+      }
+    },
     "node_modules/pinia/node_modules/vue-demi": {
       "version": "0.14.0",
       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz",
diff --git a/package.json b/package.json
index 0839dc4..b392ca4 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
     "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'"
   },
   "dependencies": {
+    "jwt-decode": "^3.1.2",
     "pinia": "^2.0.28",
+    "pinia-plugin-persistedstate": "^3.1.0",
     "vue": "^3.2.45",
     "vue-router": "^4.1.6"
   },
diff --git a/src/App.vue b/src/App.vue
index e864195..e953209 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -4,19 +4,6 @@ import HelloWorld from './components/HelloWorld.vue'
 </script>
 
 <template>
-  <header>
-    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
-
-    <div class="wrapper">
-      <HelloWorld msg="You did it!" />
-
-      <nav>
-        <RouterLink to="/">Home</RouterLink>
-        <RouterLink to="/about">About</RouterLink>
-      </nav>
-    </div>
-  </header>
-
   <RouterView />
 </template>
 
diff --git a/src/assets/base.css b/src/assets/base.css
index 71dc55a..a0d4bbf 100644
--- a/src/assets/base.css
+++ b/src/assets/base.css
@@ -36,19 +36,6 @@
   --section-gap: 160px;
 }
 
-@media (prefers-color-scheme: dark) {
-  :root {
-    --color-background: var(--vt-c-black);
-    --color-background-soft: var(--vt-c-black-soft);
-    --color-background-mute: var(--vt-c-black-mute);
-
-    --color-border: var(--vt-c-divider-dark-2);
-    --color-border-hover: var(--vt-c-divider-dark-1);
-
-    --color-heading: var(--vt-c-text-dark-1);
-    --color-text: var(--vt-c-text-dark-2);
-  }
-}
 
 *,
 *::before,
diff --git a/src/components/icons/logo.png b/src/components/icons/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..932a31c570979e3d4453e761d6e0a97f9b9938da
GIT binary patch
literal 17006
zcmXwB2{@Er)PH9f`<}IkhW=#>DPb&AMre~IWQjr~h3xB$6eC5WvSdxNW#9K9gve5s
z>}z($zMJ{(^nG8?)1%&d-}|0>wtLRGza#YaEiG22<4h0)vFd1J^dSfi{t1T|=)jLn
zubzGIgVyzmjv)j1<Hulw2mfbu*1qcsL2QlGzpxLIY$w1&0XIz(Hv^}KZl3p_*+QP4
zp64Dtee7y|-`Vz@(=)rc6_w)<Bm(JRE*pBqFOT@f8`;)SDE)RLJA$zjVj^A=x{uOI
zR6q1?<3fsq4${Z2)Mv}u;sk;NpV_64)E12lT+!s{dVK-*Y4F3#Dnq&+N8`b#o)<i4
z&+~BH?r0xg+&_Zxi?k2#3@_H^xqGQoGMfead`Lp_Njv6`a~=n3$0}E)C%PO)?)bei
zYff51h2ilmH^xyov9?Vfp=boH<dx<%+HH=F|IC+A@9_Bdx(M{Osr=jzLc*7<8M_Sa
z6!`7ycXelO&TR_mVlY46Z3v+!orZt8R4^MQq1e5Itsv~c;9}xP-w*>Wztha5G5c(B
z88+#IWI}&F5~<IX18FA>t~`0HW;0zs_H8&-RXy>N=??kYr35+R5!1%df<r7=m+mS8
z&1S#QU=VV#q{m-)*lcHG{wU|91>=e6jW0W;E3>XhB$ucJRA~9TR(<*H{-_nt>i6@+
zEkB<!l@_6?t45;Hcsxb}fxefr5!i9;nfnSc@Ex58Eh-`LkiFvQhc%M*KO~Z%2YXxN
z=Ao-9+VYYe7Gms`@mZW#_+dQ{g<JC+Cue9B6RtYraZ@ptk--R}*K}*?R@{*VoZ;wR
z!(haa5)i!Z^~!c=%z<(j_u3~j*%jOR7i~zjTs+<ojX=*Wtdw`$x1?LYqpV32uGt>v
zeCI>B3=(Op4{IsUT?t>rb@q%KD!rDG!+v{xJpMWcv;PVH_72-3crS2}g)+ht({w0F
z&Iz97<|G{5hP5C=e$K6wGkOuzq5dK(Kk2X#JbtOckN)l8@iI5_mA9OqpEhL7`5cNq
zE?eELeDdL?iwDg+K7UAq#LmH)DObaCR%7tw<F6;8gscC&_B8UzCOXbf%;(g65Nh<I
ziAcKe@%LzfxeO;4XQng{?)6zI=^nm~p(q$d=GBy@KIEbNrf-(*Z?UJJ@o6+B)x!`v
zpS)a9S~+^y_r)q2<KE7|4AqGjt9dG>*G!=#U`S-ir#Q$UmpUiy&7O7$_Cs~F9I@uj
zmCKa>G%*fnqN9P-<pylo&a;+d%!Y6bCQFxSZ{RSynpbrOJU?S+{FN4PGAW=nt*iXY
z)U%r>7FTG`Z6(;E|4qI{v@&=|RXmNW;rkM@qn=T(6stbU5fk#bM3DMI`+q+or=r5!
z-Uui;FzN`CDbB3ciPZz144i}u`Y>(g5b8i;J<3ljG-}a`{Niq{0Un?Fh6#P`hGnly
zQ2oVPSHo788Qonxi35pzlmdSXw;1`Y_9T2~uS3t4QvZCdf1m9XGLlyUV(jl7S<`bC
z-c0!XU%Y=fV(QFZI2fS=BYnLiYOK32NP-t_6stZUj8p&qr^U(XWC(FN_;@z^I(xh$
z37(vGUb!`Mv;2=I2PZ+y0H&QRAUQ}$s$VvFNZI+?uzQ?2x4erwmil7>(#ejM9Fp;P
zcpX<wl1l8I8t#v|G_h41gvW~!n9$GNa^}1&<{ag2&!Vp1D7G4Iesb*IG768^{*?iV
zVP<I(&T!-l@=v3#x3iqGQQBx0nxGCwg2B3Ck}gUtnmKxke+9fN#I*Ofz2-p-Nd$@1
z`2^>1?H?Ia5VM?(LV3lKq~vxao~^|Bc|Z94Z#)^!Vg2&U%l#*Nm3b(yHzX-W7hY89
zSi>(DrLoHx%;YVENE*}2EBQ~SKUM{9_}S^iVDI|}gi{&ok`CuESnRwjFX`{J{WZ3=
z{RBmC>=bzC20|pbvJ$2C%DR#y82ZpdbtAWY(36FeP_7Td$L6j)GKTjbbP(Xd6?;<r
z*i0%NE~#)1QF)_VzVhWVG&88{*QTB8jVrcZ0YL$8aY+J_>)u1pGImrn*@uncD;~mk
zq@NP6MCiT3FI`56(2b6bC?H#2j?o40c0}rb7O(u<E}TxGfYB8A_(0=I=$o2p5+Zy%
z;q>2IxI-?cS^Jlo7+M~PD5%g#=g+vrGNyNa68T+l9r14iXhhxTdC4L5z$vCNrNXA0
zcH~{QF>wsW&j7Y+1BCC@ptgk4QW&@?<uyCxm(Ibtrh}*p%3ayUDwyC;?rfY6Vdj&g
zB%Ij;#7~AXq2JszySS{K?*y^d7TK6fz8IS+Wu!l#4hAuyS>4Z(1ml@UYU;Gd?$Y9-
zkF5*MSPm~yg`1NJ^=J=SKXJcou**7rr?#l&-mRdhmx>nQoCHTbSOx(YXI%GJ|0xH-
zjb>~k)j&-3VHttBJj*1yxJgvVX8qDsyD-IQOb&x_&_pEvP&Rf?d_ABtrj%+4`(sS?
zrhfFCoS6t7oWz1p^5VD%3z7B0UI+5Dz_kJ|nM*|1e5!PM)8MyHzg)R8`mD(p*WhPI
zC=}H7&xnI-iftJ}SvEB}d(RsZmQf)rI2Uo|{|>*B_`y4=@Y@La%Av7Psut)Ibc@E<
zFC750)0dWjI2X;k_m10@)_JmJn&6mge;l@LeSFBNbD9Uo@bfS)wjoI5kdAFxDw?g~
z)NZf`6F1fH@!FqmGkgmc@|;rq;mI4?^Yzn~0##foX>bn0mR-&si!%XyiFJi(MmJ^x
z^%AfB-Vj2{`fE1Z^7rp|oVv5+{Zt71Pj&SG^SatswymYc6}nuf#G}vT`^I4Z@>5kl
zB^iD@u;Xt6>!$%##bQ@;h1bs~N2G+JH-s>lRDB7EhvDNetKzM-1PuoVsT1qMt3p@)
zJ*Vjbu0+9EsMZxvAHEo?K9S_#41D(sraGwF>CVAK-4(YH^{?x751xAOT&7w*%X=nt
zEYrwKzwQApH1V)#S$4svHYJ)aR9in2djBXWhb$I;X}9B4!K%^Isw*Tv4gy9WcD7-2
zWjoB=-e=U3Twl>Ck3C%U1D_F(W<rNno}5$ulP_bvPl;Xn!nX8uCgQ{-RgIJ`v|qb@
zmK*-8#*^l=za7Cu`S`kzB@Qq4iV4lA5MQ%xUFTivs-6ADn*8FiUkF(30~1<Z+4zR)
z(1h9oDn#s?<BSFQIF;AeKEb;}Izp7S=q~3oboJ%wzq%@A(s&pa=MU6qK7t7?oXy|S
z<bo#&MxH%`UYw<_1|HYJ+}w4m2FC$&w7DugW+Jd(N!&=JzaH#Q#G|iiLX1zNz#ArX
z9`t3kkKa>Ojy?sRA93Xq$FxQ*OYwdOGF0JJvS%&T?fe^IwaYtt`%mXmznyMRT0Kc6
zZd(gs#h&n0S=VDmZ3Y#R`a^m+;ICY*1~_W3BdnrBM~<BclC3_WRYQ`Bl#u2QvrM_&
z)%g+M_$okr`TYACT_|reK|_CD>ieOQ6Dv2tL=Og0Bd&@3SU~p2firiV_av#-J|Mk;
zNOtwM)3B<K?s|&VzmqG#CuK9Qc!WAeFlq6F<<G1dDBfC4&^SM_mEGpK2b3NzAOp!8
zc~p-I?!S~OvG#lAO){L|<|LgrgZ}f-b#!w^neImqI^ZUw-Nextzcxo0|F>jH*Ds>i
zeGj_D4S67Af@7NvY|eBbKsnj6ZjxZpdDs@Qop1$HDCvqw?y_i^iiwCKH%a!h)IU*d
z_vGSiO>5`DZ9V--Uw3OeL4%gR?KvZ98|Z4GxB<dyKWo1EfnSd&8$s2NF!Z0`66yus
zM%RN0*B~V|%5-5b99bALq4<zW`oOzbxNME@px8KY8<>0F<N^^>lqTTLTcd7p;{@1C
z-pIKoZ6#<-<IH8_WNONKsYBs6xFO^APIxe|yJuD_3_fM#Dj@Mlp-jMsDpV(Z)iC%r
zmzx}Ey?>Ybk+%!*+a{(N?2L8?9jEkDR04l?k3vX&=FD@@;?<m5HpNjeKT^q8bmPp8
zO(6};FPRcV@>G3zbZpV0-z<lC+?w}{F8>577@un9z>HQK=e2rE5=7ECbCl~+5BvG3
zA8=WJ+vYEILtwgqVpXvBv3n%oBvOqRnCjZK>=Y1#cW*|@)GhHcz3e%9{YqOUbtO7%
zXw)?!D0&Z&q~ysIqT*6Q-Nda-2`FJCAw+4o1Q<K!`v7+JZ|Gp-H4HL33;wofm3i*F
zx=ys-rX%GNP)jj?eFNxWcFgwGS^im6i1xH#LVL}+B5=uK7W#S+I9$iuM;Hb#-_*|x
z_tQg}zS2$wkNc8pe7KxHf-T&cD#v*nS5rBz6Vb?r6Bv;Xe(wJbup>);M>8t?>kgG2
zr`8a46^ZOLqW-fSnSoO#Nx=QTz^7`YFr)1}XYcd>2D{D_I=J-DC%nXqS%Lxd+HW)n
zVvrXvz}JO0foep`6adB6oPF%<@e({g*=|KH7X>Cvu(|FCV~{#ji5fmc^4PyP+yX|W
zU3mH#?iT4Ok)a&8an6X*ro%SfHR>voE{NpYXKCq@))O=uiy%I~QE)Qp<Tmo)fY}CI
z`qK)y=4BV(cGxyH4Fo1;kivw%|LD``ec_D+4KcOnx)a5SQv7=vJ%rU!OYf690+`>}
zk~yqE=A!b`n+xz$O}U8iMyGn%Qrp0pv)o)8MN-JMw=tH-o1Um9)P179(DjvnqtT1{
z!d)&sXs~L?Ov;(G13pljjh=|FrV3Ip^ep7<r5>(Fo&Trcwc4~Qbz{#OYoYeJM7f@P
z0qQ{Fn%vU>*C24HMY0sgK;en(a1FraP<2mQ)`tJ~Zk<B5axB8T*3iwD(IJ3strKeP
z(PtF)!N3~dBK&PzMJzfDJZC%~ygc+DRT<a#hTzU4M^7|Zep^zIe+$Poq=bw~fEA5q
zj?8s^m4|L_%a%6KFlTlf@wX=vXK0xTFYYnXb6Lc-Zyu=70|DXgS_t_o8az%6&RGVL
zgA#^o$^L4Dz$}n@qwx|`9X05|B_8l)zF%|l(Zb;cwU+{vSW2UG6xqPhC5E`yI(KF(
z4b3@_XTpOkZ5~`;@yr24X5L?Es1?O)t2AXn7oKDj1)Dhm4^mq)+ldddf&1lBFOYyZ
zYCApQgw5`_3!2kk9sp^0cqX!Y6oS{qH+gw)czvKwlqnF0PJJR@<IH5Ex%3-oKxniH
zH*S8!;1=yU;Ah23;w@SaC7XA{0Y5my;8xS4>@n08`XJ33GY!nW=^DsQwaZ-=qz7yS
z7JIu6#Tp9(433{0;@k9q`Cu!ww^&6jFLhH_rq4rI&3u-RYq#Hsy)0(-?c5V~DI|1n
zjMr$mTYhw0BuM~M*c5)h-Km4VmV^q`7UZsE-m}~2ELC`FXX5nMi8cGrLUNOoz5A&K
zNcJO3neP{$Ke-e~+0O!XN|$J}mmVxzX<0eaF$k&B#d!n_Zyhh?91czpW>z>+nmOmw
zf5MSTny94p^!FQUr{UYJl;wNOIHmrTjYH%Bw_7q?M0_JqYjkMv_iwL!`4f-IIW?bi
zzIc7X^)t)W@&v47DX!$fQ~q$HWOvoa#}|ERz6sL4z%5scjwod(nf*=}IvS(%V6R$s
zVHsMS+MdJ??L7)<xL>7w6^$27{aQui5fyxse-T&GQ7~9vG}7s4vD?PzsnXZKSHpwL
z<q^uGxwYXQKw$AbQC734{olxz76@|o_2<q_TQV3$FoxB0(oS%)q1xk77aFh9J`{CX
zv7#5naSr*~Lv?`-9^oApu06}{hGiuB$XtYdy`0YueJ#14FJNQmNfU);@-D=p{A-IC
zsQrMpJh8Qo-k+Y}7`MOLbN`KM{V5aKw7Fruhbw1Y1-U++uzskO<M}vTq*!I9ZaIGD
zY5!Pr$D`*<ZLl4+lKBCm)3X0**+OF8jy^;U=TrE;?<tnyp^n^{wqwlZCC+iAvyu0X
zRh>~_a}5eV_e~BaxIE6!yktK+tXFq`^B4bd^zP)dsv?W);~z=3E_Lffr@{5+pD4D`
zErJ&>?%sNTvK9s7cf3A)Ihw|DO$Za2w@A(v{gyntTesxZUc<GdIBqiD)#cH#J+fhr
zt<dU(QMNW`Rz2f9E>Na|c!_WjLOtKg?*5ELjt;mPLa8jjjc%d}J6@hqUc$@{Gg>v%
zG;26r&u@P7SRci<OO}uGem1pTlXOKkczdVjq72lTRJ5O?h$`%x<stZ27??Qk%@>bs
z92?USyZ6woli$HeMVuw<%>8YL*Y9x07)Qo$2dOD|uv5<5`%_EQE!*BX0&8g6z1!@(
zD)wKz1x*HI^T&OsjCCNYGijRP7Hd4J7ZJZ;-YJ>TKz6x5>U7mRGay*KF$XI4EQ?Sm
z&xo4kCe;4%nz2-Q*Shm`Edi62=juIc^_WGM9e!_7Fdd%$G$^{E<4ng$BH!Za(rf%=
zjxzXd`eUBt5PV~!mH8qHhXT@_`{`%3pm*Pja>FWpa_LR^s?BTk&ctcs&<5d2BdBiF
zU}z&$o7ipv((y@)gPO<mJd0&Swc^zqv%@Famr6ca%jPfU`J}gPGa7$irRU#_5F%{n
zJ){Ue%w1BEcbm{CQBE@K|5pZ4R=tvl%(GdaJmVMg_E(8#S=oiRsD=r$l9=D;`*z=Q
zM^?q$e+ZIMTid0-3LJk2s#z5AK(pVzK6XFHDgJJ1q%Oq2`7KS=3c2)@nXCJ(NnM3v
z$G5eGu5ZxxFzVhfdvpUfo!6tr${y>W|L=Sdtghjm<s!gjb5oo<n$Ko0Z3$_5NVxH>
zFAf*w$uW0$mT#2BCfJ7W^YOV|+>rnDe1g5A(r7%E#=r7Egb3-mq-?)_m}_VY?)Ag=
zIp#ppK3k-1f~2zLF_SgF{EhyinHxn`9QKcgv$O1XX-o<_Prx4QL+#(b8X9Xp=oskD
zfO>kUfk;8k&YMp!`KHRbzcp-C>5G8RSOt#3yY{SRyE$re2WL46Oae}Sw_O@49u6pv
z>HTsX%zC6nr+H4+l`mw-GoJmzLZ0t{fmnku)QJ$uUrosP^-AorYK?m|+;Uw=>?Spx
zUuo~PqlHB)FFm;886P0}BT#YNSJ!E#?1ZEc8Eqoi)1Uqo%P2Dbti+7)LiRt95f!*x
zZ~n_BptQaf^Tv$SS3Q!ivGw@&e$jypMLSp>>YM%Udq*0QKe?pXq;xA|MhPQ!?Wt5*
zIO=_7TjF|x*dywJ9V#@`>G@|UeoEgemAp&P*DPBi@?jCUx|>}KMC}w>+KM*Z-P$0r
zdg|GwVX<KrKd|e35MCB|aj!$H)!@qn`&EC-q+C<jS#G9!pT5_5eQTPj4uCpmbXD}u
z&n1fvmkN1M0T64wQ!>HsRYjXWu~cFQ3%Rqn3$jiRW#)5O#FHJBd6Z844NJK#L+0n6
zQ{^oDIeZJ+U$Rh#uq(RJQ2WiUEg8a#X9}?0auR>-P0Nc%Cf`PPVg{<51wV~F;`*?_
zj;%wK5NW%%P0SL8GIFq*k&;!^bYONoxxgz_e$@8<no7RfX!?KpwI5lqb!|-OhK;=f
zT0z4%)?wN=1)HBU=A}GY%k!hpd<pWKY<1tQ6CU^a+4G>s-OFwItOT{$F=63LFgjSY
z(NsB7U*l61x3)VhW^eqxM%ZQNpFVvzXniYk7q4H`q{L%wQ)D1E>n}CZ>r=0^pTt_g
z4?{@&1lK?99};F?zIbxV{K1imq%_ydLFt3&bL((f>pRT?w>&9fsr8w|+qt5#ZvDdk
z^r7EguLx*p^!vu<JhM3UN#X3fkD*hmAz9{d=V<#4VZwU`-*a?Nw}n1){RTPVMTX}J
zTAd&0epY_cb!c5&_3WD^ZnrSwT(>Ej{x>++n$DdHL?q`I-zEl354Oo(I@T;+St_H}
zxFtgwK>`&qcB__nF_of{hWB#k-8l2wYJIY#Ht79i<-t&}<IjFw^UjYuG$D(5{%fas
z8^#>U;T#`V$b34nf)AZ&c=y?UMR(AnBG{fyY(5G9%Gy6Gd0R6}nzw;S%V`PEzkd5K
zuTJbL*8@{1EpJY-v;E=Zw@XtDezTl9KUPB+n9;tT2lmeC7<U!UP)0uUIuNEk>;3km
z_A`VMo-*qtmv#+k;rauA`!{nj>l`O@+=pd^gl$@%jl7y0yFAoMyRs9T0wUXgo_s<=
z@&(&b!Ks4C;t6(6UMR$ZzqbG*_V6|Hnjyq=oq20fEBhqzoXG~K>o$|H0624hFZ;I{
zEG@nxaO0?wxBKa>CwLw6VE)SZplM=0NGO9YR_<h%T@*&3s!0d2nuqwI?OI<~|KL}t
zP`3KxpYwGsXTwh;3*3;P`d2?~i_P2)rDl(U4}Kb)Iur@l>s3o?K{o{qjAid5>SC)}
z-1aQRM$AVLb<tVwyVMx(<GmJLNOD}U&~%nF^vY6*I-1{z2*u<Ag-EJk&b;!^vi4p3
zY7^c}oRLE*EKG%Aq&XXeMXa>Ol5)FxLekl2Zslz%BDs9|ZY!RttkDouaJ4;XI?-`&
z!yw*_={T7S8!AACXnQSS88R~+^u{lvnL@SOS^mE5&O@Bc*+g4NrdhN|?1-y&oY9HQ
zp3vQrfho?8rhcU$1vm#^8s3J|+;vfEAqOhuWsiQ})`@In#m?<;p_>qO<MG!bP9YiZ
z$E&_|TJ%buO7=^Pm@r4(@n!{)pxCornmaYLvQwW*<#Jo(J_u@Hxc<I=R*E<WO5u4Y
zDm=0#)WWT`OBzBk(iIKDXSe+slMCaZhC?MDe<EJ&CQZ%IwaR(VnIGF>{Sl@cCmKc=
zj*}x*LQj%!kN89JQ`y&QEiqhYi;Hj}?3?rRl4*$!mz`bbyfj!1LNOBGhYq67+1;t8
zuUnEDX%UUQ=)t?%>aVW++4uWxJ5}N4?m*GmQ!(0NlQlI?0zS`g`uqFTMMKKM3xc6j
zA83vOICUg;=w`}#m-h&{AAWyV@=2_d6S{DCx`K<iL`)AJeM5ag61tp+VqT_5MrumG
zh`U7>a9B&Pd{3|4g}K9G9Q9uNi~hw!SSgR?+~RK<BBQKxC0Qm?Z%<{mb1u>7?93h_
zc|s;Mx^ja3)N-4`&K81(NRPe*y|U^1baO94+5FuKEb@Z(#47!t7h6Zil#_XI>?-S{
zMuK@KP5$H5skAHOjq|hP2)%L*=uA=@MYax}|2cinFngVkF;x5f6K0J>lK6i3DSZMr
zI~J$r1igUqLUy;DD92jM?aDGC?Pp*7b~Q?jAeLJvT6RLUeN?J)`aNk0u+}=@XmTLM
zVz@}8iv$#j5fC8b!U8}zkNqjgR_!O^f)P_Sa*|m%1?jwBV!=XSOeCA6BvcH0tKGNt
zBHSJ2CX5r)C=FJ(>Mj4brIvG*oVO#bcEbG6%OAqx!3)%lwmkjnE{tn5ceuA?P+M~3
ze|Z3y$Ww@$kl-eo+<12;>S)KNet}$@nhnoiWW|NBs1Wd}t%E{ftx56U4Z?Li{hK?d
zZFz9!-8By5k1%3Sd^DOjR6l{V(6{w)m7YO*5DrN|$ig!BA$PE1NHFExo_R5%PRS6$
z*vNFw>!Cv#St+X1i^y6=+1oG<LXB!V4-^VxrPNf{-C-`<+R^;W12r?!go`3GQ+c4Z
zn+=rH`xfMo^GyHTmQ^Ee3a>4ZGo~lz=EI9V>{=aTpG}d5yzNZMe1zz;%77XPNZ<E3
zncnJ19R}HQLmK@QO&f}v0NUYZ15uZg4;q2aBI%R0U|*sx93I>U9@9D8nm+L==9l9U
z+Y?5ql1~$saO1(#IKRwhdBtnSdl(z+y}90}IDP-Bv4!Y*zJvGajquyQn9$UsG#lGj
zDZr!N4HO85E6*Pq5!VH<(tq2oNkBmg(`@w5co1BKo<Vb#dDfDvXL~={QqKQ<KF5TX
zV+hZddV^0LxoSc8R-$WFCT{BGvhIiVm#29k=3%`Le>n-ozO`DqaP8s?hx9hmB(u_z
z_cBtMP<a>75~RP+C*x>HO!+vOW;<G+m|x5Tfy+Y7e(DL>X^G=x9`X5o_4l>xI3^r^
za~@#<f6HLMYn;(|21@RMC-Xp@^Uchvrm(j^?02>5G)*AoXitT4{=0XPoH|u}J|%Dw
zn)C@_&LTkPZ9YL36U{)3*j6cz&>$0<?n;CDwfk=1XuhmiStyH)<T29#J6Hz`WuN)s
z3q5NJl4KG^#ujMqz_bM}9Euv-{FK*?-fueMg=TL660a3%_Cu+d<K#PQ7hZliP5fB&
zA{_$Sk$S<bSm5n@bOX`hS}mFVOEue{iXeC9Wc>RFVWadSM_rQsUo$ts2Hx}Fpd1LV
zty9W<2b!+iyf!8wPtJ>%ArYaulMttPcT~zbSPS{l0Fn3GnYO(JvJ<>9lRDpx5xuR|
zK)fp0%?ov%!y)xQ=2X&(9CWSbeKMDuybj5F;q)+K)|*$*1Mh;dH*+RF+AjXs3sz%B
z&#rgSCFiDSBO?V=2N`dBbg$;gx!OuoG}IBvwU2xk>we?4`<A*?anFN^d>sL1D`lH^
zp;G!SU<vw#qg|ZJB{-?yt2=4gi<B;1Sj!e59Fr=g`jHa*Wr9Sgus4R{@3v7F;oX|^
zhhkGFNO79J?6|vBNr|_y@!;CKS9i{(=1%PV4KPqgh!_IHDFmrcSTI(wT-_BM-hTs(
zu?Nr`7Y_gai`FIB!{A`-jq>$q%BLha#{%_fX8If|u;+O&_O;1g1at)(4LQG|xh{&l
z+q+mSaC`94Zu6~y2narXaF{Lm=&KbcfltY<R#clu353LpA8HF8sK|j6$F6Si%w5cn
z&tu2uOAcg47>9WN9}9qGrNoHO1gL>Yvs~!6CrplWwn~lA##}Sqj>x%qqvr7zy0#!G
z4$`my_Y{Tphv^Z)*z6+`npi-B<@o}3+%Sc-`M@Ibbj644M^9;OsLbR2N!jdL)rB84
zp@{~P5Ryxr2j>PJFnqp-sG};4iUib}K9R()sjz+MlRP%usRr@RH3Jeq`9hI5JNM4~
zpsdx}n$hq79Hd8MkbgI|G#<<=QC_&H+-oh{@jsSaB=kYh98-A4X}{`JXz@~#wtCKV
z+`&B<!!RzI7b6z;ERYHP%YOIFd)Hn`h|PHW;H9S6(E3M8Xv##AN%;5gNq3&%YMF7&
zRHL-*K8>3O)D@4p^nPRgAec;ygZ@ccNmV5t9HpVg-K@AS%2hOVYf&nuFg%KtVwf?v
zufAEtj=Mip%nk&lu>&>6C2r`3GC4uCst6GzXWODU?$0k*awg}4re+CMZT0AX_M)OR
zLd$>MbX)g>rM&mR#)p%sN&jm|D&AlKb(}0dwR@jCWPU^IV)5|I*;`2D0HNQk(OR=X
z{Uv;7j3Ahnr)%1g7&a+}jAT`kU${uI)@V@w^38mUxlX=QrHD<7zbO<WcJ%RCC0Us7
zI61Q<&@S8``HE)iE)-=^nyro2M6rfydt6vx#F>Cy%QZ)w<wou`R8yLiXMwxLT>F*!
z1giPHs;g9_9Yy){9*)a-vtWaaeEO!6cD#4xX269*gZ30}I0Ss9J=HV(o8SGPdTiJA
z+_mluHlu5>F5tl4V<e#f`vG;yVT$vX(238=AB`Z4*!fEO<n0TGMU5w4t=k`wi$79r
z{L%3{ki)eGb?bOl!M_vxM~~bUsrnXqVP3B8WA|#Opq3MvaF+cK)`C6A?z74#Q1~ud
zfn|Ur>Bc_K@0HhXnzb*kLlc%M|AxM9!*Z2bDX*Vle2#>#orQ)rRyy^P3^Bjb&&eLA
z;lViop$PC|eE$uWC3-LVyyi<$E5N6=S=G>&XKlt~)0BLCjIR23{R()HFn=ES;>?m;
zh-6csXWr`R)&uf-!o@vFCKhxY1T|J8SOya-wtnEKSJCm2dO;f1%Rl}(>!+nH#EKng
zRW*6@Im!?+&-nMeR}~`sT8*~w=!%axq(h%AfMNhsQiX%I7_4kA9RAh({<-=f_h+x7
zn~r+HDNU-!e`f@xe43E)by2yUqY>@Fn0zKZ9l}iG;6azcectcY=;&tw%yoDzEfpF=
zTSid9c5=30djdBXc(N{fP!N8<sX5DiHIfMp?jP$DGmJnnyL6(GY{^L-w)Kl>i{km}
zz8i^k_*r3TEcpU2HgtK*ht@f{cu9tSX65Tl_w{p72Q><y1b>BC@WI5*Q>i99H4cf%
zo9-yS3<%#6@CcFYcbxp|%xpk=PnD7J2KQ+`T-_BwyEh|E@Wl-1y+(uOzHR-wLsH5#
zT-K&HLme{v8l#EfvUj!!ubvmc=|9@Nt^=PmfIQR~$2Y}r_$AFY$``OTF4cyMVF9Mg
z@7`}Mg*Q-@bNhVt<p?P1O6T5nft3d5UNxP&>J>BpA`*K<GS;WV^2g+oS&8CJZ$ur{
zwtY{GzTFWz)d#low?O*O_k+hSY3oerg53=V1SesdvAvktO8COz7rzy?|Hi7m@Y+c%
zp7HGSE+ccAn0=8#M#|Fn()+;2p$mt$=yr0EQMyE4<q5W7-K_V3{+aQs(iBoeFp=dp
zdrdF)(5bAjf93Ufb1wrD+4>p2s(+lUA4vbmvbfghjARw`q66qU1N8aC7HwT3_~!W%
zGja%w8~30kB6+{_%q#8czdtv*l2z?@ufM#)u#)qavF#k>+L5#&_+9ub=T%JP)cALm
zz^4#xaccyoZkRpxus8j&_m4nT;#8L{M0kxdX`|p2_6YcLE1eu&rDV7jn=0eK$kcQ@
zx`mr@xX?7o4Zs6ppVD%-R(8K}uNC$BG9g2dGm?F@R#c+4jAKG~b*(CP9dqI3)WN64
z%axz2LO~9@*Ne@6MpN$N5LSIB9zJQ>)$F;C5fdmpJR}AaF^=??*W%P}sjM;y4`wmA
z;<cX(_Pzi47t4wbjjuQ;2UQ;7^pnmohUfXHA0pt|B82_NC=)K2XsESJB4gLuX^y^E
z9+rMUJGp^Vobfo^+UEN`SoVZW?|rkm`MvPGBKC5~ZzYNJhe74!eLxqkA#TIw(Sb0~
z-#gYTW^PhL(~FxtBi|PVB~zFH-<?fdYe)<iKi>>U%`z|Vnw^)FfOwM+Ak6uh)fq=A
z%HsjctR?Z_m+bp2x4{)|Wx*^xE<hP`K1GSoW)gu0BJ*F7G%wAblfSHqu&QrjF6G}I
z(|gHD@IqB99Ys)pu0Hu=QG3j=`hzk4Bt3WQt6~|-<K62wV-8^N_|zv*SDO$bc1j}*
zXCpZu-Gg<lMt^iE?ee6>CqpcE{ktM?KRInU3H&F_`BFV7fJ>R5a(XWL{q$r26S&Vw
zF4e92ocWA593uq%+S^SECieX4uW*>48#^WP91h`%oV1<^a)#!Jn*Ay}%maJeUSb<#
z^ax9ZdY0#E_<-*RX?(x}d@4W9Z3N1=8PFJX#@?*>mnVJ%DGpsSS9H4gJt9Rh3g6h-
zM&C;)+H{1nZLw?L98U&s2w9i4tjx(xjb?yEeeGFK@+RQcZrB+!MxSc+17dm$u$xP}
zMuyE?gk9P3zv30J+YOc~rr$2M_3g10fZ@8O4Li#5VTvZTL=mD9w`kvf8oOuNU|G%d
zB$32$@M`nZJ-f6v2r2Gr!xTlic-T~wB<J28;UDtHI29q{49?d_?=}}H$H|ik6&1Cj
zOS-9u@yv^zYAo4r)?<*9ovZtT8pjT|)q!pWzM1|Qy~|dhPT<AaMoUY6oT@z=A&w*q
zjzkW}Q9%BAa>*mN*7`Gv`@rDko0^eJpViawyUvDJJSo7j#GKWi<{dl5CbkaGuP>FN
ze^Ag_fM*$gTPgVY#JjQs!1l(LOZJwV^#3=<dFk5|MY8+?wNNE=Nzog71SFmf?wG^7
zqO+=pe%FumD(2#QI$UyCYenB{HdszJ9la0_!_J{JEwl8YVD$@yC%m$bH}et}PV~OD
z_IkcT?q7}mBUe@L`GRct1>$CU*(I=JIQ0jq@88URtHToTfJ*~AL(;`Ic;cxuAdcX$
zEDuM{%;PlSPi}sn!h#6~MdH*tZ;JXIN$66N@8T3cF_&6aRAzI^{e0T8hRB*Si6g1z
zOvn@zar!vz2&Hh}ITH6QH(3b~L(+Sg!^!(&N6{3gOTfgJhJQ@kb{??ly;|VKJ$TBA
z!WwCc!Jg4+HCIFS1C|dQPfS1E6iP7!h0>zVxFcU@^(<<kR9UN}=Ql^=PA?JA<Fw-O
zA9m}}nE16jVa)!6$IxP@CaVwgCS>ILHO}FWK4X2ed|-3ZgAc6CN;*Z6A0GWcRI8-r
zk$YF8Y0JYN^BxU&;-4ATK<!G{4%hKoqvZ{}w(Tv!U+9iWpzISejJF%Vb?&q2<(jH4
z$`h}GS~5)ezV@L4rF%8{g~`<+Rd8Fbm-~?$7x%rxpLSPMy5ltRukS#za&TYM;sCHU
z9R{tsbpH^3{Bd$)6`}+cjekFt8gsM-3`tC`-9Jtz47-B*j_%2ZE^Kd)A{=vj_dtRZ
zwEmqCH0ioS1iqElg@fBKgg@gW53ciG{<wFIh_+b3Ex}=J^OHyYg+#Ge1usf9)qyD`
zJNR_V7_+#^Cu_gvU(NcoW<r(t3-QVFQBVA}o?@t7*=akX{$$=?CC%x&aL3Hg7wKfk
zU?YukWp7)<l2$>*@*<_xlWm0MEZ4C!Y>zgDE;m?4+S42D=!~6`xU&K8dOU%A&FA2M
zk8{B6aRMcQs`RmrC*Q5?1Ev3v<qeBLd93`GpVzlv4HElfAs5CsMuFw%Wc=LR7F(~N
z_<L9E@@?`GA#VS8GmMDScb${bz$Q=EKm1*=BrtX=W&8^ulG3LRjcvO;<s9&y#Ts;U
zj!_1uwYVIGG!NoQ9C~XW2TmMxNo(#;2~zUoHo6hyNAHAn6n0z&`Eqs=kP@Rzb=eya
zDg0A<ma}}VS=Oct0{YQk_q<D%WEgjRgL!86*f>w;u|ufx`q@3r!nyYoCB$tY>CoAw
zBlA*6P!HByH-&=p1ox!JFR97nZCdaDbbtM{y?-+vl(@XUUrUqPj{=i6OsQCex%@c)
zha9bu1|hxMc~o%+auhxG56Pe-`QmEp(q5t^PG)k17Tw=cy4Po3^#%(VcDnXks%GZ!
zw^3i1iK54Zf?yc{eMEYsvpRiq*qjD_AhprRXM_msLVA=LLg#-b^I8>wo8Cvg@yv?3
zCAI&}f?zW8<hGJ>)M2mZD~C``Sd4pjwtI|!6S(;;R`xa#E%~#f1;UkAhp}nHvw)g3
z6K=@M#drK9xcB~*c1reER{eK%a6%gvSRE(TO7=?r2A9vMGI|eBDk%zQ*dM2!%2-JP
z9wAa9oRYe{LfQ@2djjS_HJ|+9qIXV6<9B;F_$h{ewWum!;mF)k)9Lxj6n_U6<2o9z
zKDA4|+<xTpV|9VN1$dh|Ri2o6oji@YD&3d`LDTRZ9W?PKMxrN1p8Uw(pf?=M5#JO;
z^^MJ;FgcC6kl)%;1=iiU3ap~zxsd<g!q?4XxnLb`*$?wOwll)+OH2^@?c$zTelOT_
zs>2U~ypyI0kEsOT^5A>No41;f67}FN^i<_>{qJD2aR36)Pf(~<22K<xOXe)YFcH}8
z;{quNLXFGwn*p?k&c_+Qy02eXhp9H#bAIu`uSq-q=8Td*ytoSMS+&pLIDJproOm-W
zI=X4uMvi<i_T@?UNUg>$#}FKPn?5UFcNK6q^1<y<MHlDdf8P3)sxHTkHKA|OCVV)V
z*B1G_F^g0};?=4{{a$oZ{drcbukgDSavZoiX{-d`d)_^jeCqvu`3ZeK=gGHo<b7bl
zhOXIGzanoqsqh`ZWoHkSI%B8Q0l}eFm%{D;<LfS=s$Ip;7Yd9Gdu)>iO8}ePRU+yH
z+rd)N7`-uRD$S*RTF<1wEcFB9LB6AUPo^;d@nDMP@raoU`Zp<7G)`10JaHqsN8adH
zseK}e-{mbH4mnR%xDo@`sK_7}SHH)q*2gz^gsGZE=O~gNde?G)Oj-^Bi4<GWg?CY@
zZV{+H^_)lsrWQMHKl@P=yDcPoj_VHeFiG_Q%W}TreozZJ{(Lr}&(Bu<JCGG)hqTR)
z$);NCMx7KU2v;!~g8K>R+3ky$zjfl?z+wRK4Wi__aPw#qhs(Qixa?xJw=S#*Y<_Kd
zOY~!Ap4D$*R%|Q&o}lshqZK|s`p~#W9td}G*seYifVdi?C$^Ms@vE#hlYy3^mVDBr
z$e&2uF7I35vYt!BH-itT2#VVHi&?Q&xHvKhf**p2s=-I?FY2oK=|Tu%vADR-_Bsg|
zh5ln1VZ+Y0kBfAhH6Y?YKkfa>yw$etRc%n?od?X+Os7HCPk7GzR%^K6_q(F>9#ctx
zn=CH;#`5m1Nr14TMqUUvFgsfLWBeA-6NkS&LB|D*k|YlfK%Njgxx`#7O|ArNkDh}=
zT?W%fb*5leR?bCX=fTzQg+$#3%kwiKCT$`9A==LYs!3_K)0@q8U)W6FX{kOaLvrK0
zI$%VM9iwlvocHAjl8DaRT_C^T1FlZ=0aW!b5gan@Bn17aTB9zNhM%&yVX+OMgF1v)
zaNLAolQ_;aDfN~=WcQ(B48+(r;YAU$wrfYV)?zx!Opx=&A8+D?;hR7>nB~_&A0LTZ
z6M@>b3eMIxOlu}>2#G!ds4b|}apA~wT$eh_7ULh@&+_E#-Xf4;5c!5v&Al}g1dw3N
z=kDb<Q6YkOOg+w({iNY~I2QQ#^Y)@J%C}+^SW7Ha{|*E_74&VpBv%Vyvf5v69UrWV
zN%PaO{0dIZpeyk;VgHUzZT&B~5Y+XKM5_Q`=;v?$tiW9oG#q?`1+MMJc~2n&UjH`F
z*J9UgVdeAB<H(co|6pK&%C3g;tQF*FGN9~p?UkX(6MiAum4-k*6AF`IRuNQ0U2ZgM
z*3@nv*>daagp9xz!_cK0+d@cIeKUy3nz@Ge1qogXV88lV!F7sGJ;?#YTY#wxx-a(1
z<d4*j8Y0jRzzW~kbsoJD0MPAI`dLL<tSKeAB;o6TX&G-g4w(%|rpqX8(^O#fDkVN!
zvp8XZn){U`O5eNoquyVJ>Kv}VG;v0}BavT{L>@)%!}FI4ZJUq^oPQQjVJ(3dXuns`
z@6>Dp&l&qS=JL?U*qV^&6Maj-I04_bWBimAR7gwUYoUu7oy$}+sxl8053@VCMYtM!
z7J?d#!VjM}s4jsywc!rHJ*!1gxzR8q@7>xARP1Z}zdN^?S5J}o1D?Euk&n;5d;3dK
zsS5UwyI&(ztC4#5Arl0STBjqL8S+nH{UhJQk7Hb)Qq+TiW#9jr;Zz<EGJSt~ouV7S
zChtpXjoAE=07`wY+qAQQ{?DowVA)bM_p*MjX@;tKmK1<x-ah-H24?)p6M@&dS%E8J
z&z}I9u1)lT==3P&AZ4reoz3ML@KI^Iv2`tHVE~saTpSLf@2*a&A#Yu?Y|ZbDslvBf
z`VV@(&2F)QLFM!D6|OiEVC;bFx3_fRk1~B*oBvJR;;4-+^-bIq61xr{rLyKD;gePM
zJqlkQ2KOQ2RoA~<rvMdV8A4m}$?hd40bs(#0cog?m9N80D0l)R_z56l1C-=P&!@fI
z_YYVA&ZQx-`P(JAM}*Pgh0AF`_DdTx0m8e_0ZKQ(F5Lcsjdeg?1W4n8Rdwy1QkD~w
zcG)8D#L56cI8KKI3Ihi-zqNkZz51ca)IDL4EU@^(hF{*W*t8zITzCTkoqa96N!PS`
zZQOh8u9&~+I?U<#NMV07^}7mFOAr2&={7S}HGuuGojn(*00KB2KAO=B4>@l7s;s9^
z$PCz;F^mkw6waL*)4?FDWG*GRww)KW1P=1oR7pdeV)eXm#NoUWwl$y6xjHuUx)Eq1
zKxINppL5eQkL|tzd+-an@Yj3!GNpVaZx(#B?-(uLf(*qA<g(4Ie53gTdetmctrRoX
zurj}p_Oj07+!4rm&o4b<=y>>RgaX<!mTn@f)SsS2k)k`6l*g3BDmqllWBU#}gk^w1
zoZHc2=(JfMA+9E1?p;HSLr>KXqRd!o{{k#^=hu)6tFQX#&_G+3`(HH4$NF`W0Iuf9
z0H(;%u*F?*eD@tdIthxN*oapu6acxel&d<d<KaSKy5AWBjR9<~A%0_4g|Bj)>$P-k
zk@n$5I+&Bt?<;Du)DK&q-kaHosrJeto&9x*2N%$;=DAJz==uZzn5zYxCt@B3_aJ<F
zbkDc@2R%7)ZgK$yV2Via>9h53tY=4;t-?uyT$Xvl-Hjl#UDQ)=m92_Rj}UI&JlKp-
zKec{@4TT&ZFUJm)%TXK}IyMtDC;w(dDc=$vO|`|gW+BVZ{U_5Wti$-32gfC?_~f##
zj5*z08n1VEex)FgaLR<t-4aITbNAP|SShqrEc<YQYm%I`;CHsMyN$%kI<Lh~(mnKP
zVA>}4CsbHz6>$oMXL$`^@=sOlI@pCg077wc3_;KSz{?*vsKPlc<8%3#%4w`0Jt5El
z#*d5ZtgtP;cl`a6bato7m_8rCk`$+JX_sW@SBRh)<AWZIKY8|vE9%b_V92u2sCl!1
z=*P{BB%5wNul@2e`MrC~ZzAD+l<1Z~H<dqYg?@F`bqr&79hM0tW?p_H3S)8(xfheH
z58LQARzFGuw{Ut$HtyO`%FEgyZMS03H=s28bI%nm3p)!S>%GzPUW=!KfKpozlNh`b
zg0cG<kfD9wp8<B<K?dkl@Si<7{H;^L5rjlbvQY*s_u5jM)OUn5Q$S-)AjrRtUJEe$
z&Awew^7j(Oj{N!z_%b28bC(=2+*B-MN<zIh4Do=!Zi$LYd~`EzA-vutmn6lN0}_OR
zKa1e2lJ}rdjiE><p#8mHcyM?rszma00^;Qpf#g!;vrcJQfe^vyP(~9K+ZuN9P#%pC
zxl(lQV@U^3P#NUI`jDx3n#OcPDRDUCjqqujc(az7(f;4HbL_RBwV%w%)k(xu6mGP4
zz1*PzeJzI92Zd>}*~h!ml*#24&7Rx@2Va;Ly#X(_*OMbtItMbW&vt#N?pQ8f>)KMs
zFPo(Y3}8ZwBN5yt+?0UsJe&Gc!5r8LJjdS?y)m;+W?%=sK5{#E?NtF@?QqnSe@!DA
z%4)rDA&KIJP5EnunwrYMSaRA5K4~2&>tr3p)`3&>`pIn}%ptg-yeM$aJ_9SWDmtc@
ziVb=e2(plo^RGM#gNZmJ$RVYs%HJ(vAi`O20(IT+S1G*ft$Zn80=UcVE#zD>{ZHms
zu<bfB2kNP#YAEBCQMd{T6;^H7K_%*>NDJ|5gXQ)Y_-)amj)V<K*9lbMy|KH~!tKd(
zV8Vw)`nVfIlb7V?bJh3RMAtsUZ);pi^j-8!Si6TlcILke2d6*0{<~TEE~eTZmNs^&
z5Gw&)^w>~xT{%A`3c?8L9q*Pg#cbYW-FWQ2*r*|-Qoa_il;rW955SHIhIg-8kX$PJ
zWWM||fNGb9MaS8!Qb=cM2&JH=&9x*D?=EZGeF<2EO*a!xCU|IY@HgFx+8C$|5?+lz
zeK|UH@z7SJ7(RY?sOHJfo6_!M^rLgA>q?-~JNfQ_@xJVv%&>qZRHzu}C}D9^>2kTP
z)~P}5X82+VYD@Q2-W0K)_wKf+tg@8&avhTU7r!H#CgWJh)P~eXefB>;9ofU}ex#tl
z`O5?@dI<Ah(}W-A?lJ(Fb<w64xXLPdz&`1yY_LKuA&Etv1HE1H8}%Li!IyVMx+YQA
zEkUI`Yh}ku_`!OLEwC%)c`kJOzf7Oz`E@yu-3}!3o+G4&@oxwPg~prU3z@k*zjGl^
zKHkpAG#-f1vJ92It(?^ICKX<+ZP4D&gc|?6UI~9JC?yh10p|c7lg8&LcYjwd?cClw
zl5C_<!3n9tG60qRE%$Ui!P*jRlgG8LKY#TzfM=$H3E*0tv8$xe|G$n0TPqOw!YdV=
ztMG##k<Eb1OQ0hWKR@z2_aewlXaC$pplw8kb)Eh2;@3}ZCSWiKk;~LxI2g6r0@T>W
z!zzS12JK?2cTt0lT9a20BKHAOY4^PV_4G%eQ)#Q{Des$h_CW)N3=fX>V?p6jQO5wi
z-$U@OQZmPE)YNm`Ia^gOI|)c<I)7dg_3(t-?kj*XUguJ^y3X(_>ESO`;+oLaRCO@n
zFKGT<cCAm_RjgscwS<)hNuTC3@4RSvlt7Y2egTA^4{>+bjf<$zDN!q7!ZCE>ex7kI
zI}a+^wGH_9k|Y$)u+AB4xxdsSC#rSvMD=e^LBeY<K}XoumglqHD<xJsQaMm+9ks(V
z<h9_|9V@?_j!Yh?HD<Z4OwLpE$@P<4LRZC@Bp~r_fR?lP{k^iOZs@U#8kmAlujjqp
zFRvvtM<~{VX%qgz3x$*xuCt9AEV}~0$Pd-<qB!pvQrJHn=GD~WhS0CHs}T%YkT#Pl
z&F~xM6{{a3#BH$CK`_!nbqlo&?HPUIhLG6l(y8Gi6X!F<e(zeDef;RTGI@A#+;IgD
z2DdtfiFc;{0HBF;gtZ4>$H>p=CQ#nsTmxN%e}SBGY1EEwx!i%o0?BOK7tPn$(uOyP
zfEl}K@V7AV2{KRfEP$>t;lvvMd+l3a`>iXHzTEL{x2Fi99JKjT6(KFg)m8TS+3<9@
z$eOL0^8^z^l-|-Folu*?-YqdMEeS~ASFh^wHD~5P^DoW%u(vUH*|&w_&Ax}Tj@j+|
z5Bbw`g`z+YZ|vxZ-01kN<lM-q3`m<rg}zj9HcGB~$M;`N2aVJLZLOE(jY};BmCfib
zux#>6K$EQoR%5ia9r=kNZZ|D$#zC8}!}lMLoIE=Gh3t>F?0ki^-)ufu(Rxu|Gwp=~
zwL)%?V4tQoy9y{2Rc#!;T~!Z^dPX!xptI)K$4}zMVWmSmF#$WwwKSorphflj&!t2U
zQ~Q-F#j77S{3ReRp&|H+Uc4$goIa*_&SfkVQwVyeWA^HLK7TV`RUMDfCu$+k5?wSZ
zbSkf1$LyLnBIa`a34;bO4l3SOtH1tP{=4gYK0J_k!^8F?7Oxjw0d?V4AELH24-H^%
z`KXZJ9<x>V+2OeT15+MozQG~>K#n%TYK5L<@u=Wr_pwl69%$e?=p6|FjUt}UEk^tM
zGohAqY`a3=*<DmvRK86&>F%<frbak#qo4iRyd!ieX3C#_pNj`~Ljq1j1R%B_$NR>Q
zR%*q1%(~VFg=+5^Len^p8lBbOOS5C4Oz4FzxbAb+@HNE_)msK+tk;eOXmvYAum>_p
zO7rxrZXWj#x_H?Lf?&U?zZXD+INQ8q47Ib&7HeIHt0J~(S82<WF7Fxyza;j!`x4v$
zApt+l2LfF1()Hcd2i6C?IEcaj#XJ2i2-{R)d2ChEe3jmr_|SLNb%Y=j2~I}+NGgiI
zVV6x7K0a~LY<GS?0U^RS{^}E1x~Z`!=0Iqbwj&@6K|GazW_M_ejJa={M{-7T3GaWA
zJ{?z(b@ZWQ06U>L2_l$R5qU#VZS+P<tQJjqd{L205IMLa-DTUe{RqZ)2%Am(&klRW
z-Y@bi>CYT06)zUVgnMs0xRWjv`PFkrwb-XkGN9uA3DxkcW3l>^N>;4-yu%LhM?6!y
z{MeVPGMg{3U%<U?t;Q>DAHbafwbd41GvdLeOB=?EWeGZHd6eaUJ#dzht)#7cH2cOj
zJF=JhxC)OKSv07c2U$yp+tufsX6Y|{_|sInGmP5$s=WyyaeBkx9Z|a==ljfotl#8V
zW<8=1)*LgzT&9xWMZDJN8X*3{=5Euy=3@+7b;+t~mmDJ~qbwJe_DM4Nt_-9{lKCXp
zFx9#=f3-el$HoHAq8yH=aNm>7M)}hGiv`Y-r_30iv%YvLANb@;G@t#_K#LI-Og?7)
f3g6g4VQ(T^x{L+BdJKLy1f-*R3zK`r>iPcwZxoOC

literal 0
HcmV?d00001

diff --git a/src/main.js b/src/main.js
index 4fb24b7..26fcc4d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,6 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
+import piniaPluginPersistedState from "pinia-plugin-persistedstate";
 
 import App from './App.vue'
 import router from './router'
@@ -8,7 +9,10 @@ import './assets/main.css'
 
 const app = createApp(App)
 
-app.use(createPinia())
+const pinia = createPinia();
+pinia.use(piniaPluginPersistedState);
+
+app.use(pinia)
 app.use(router)
 
 app.mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
index a49ae50..5d1e0df 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,5 +1,7 @@
 import { createRouter, createWebHistory } from 'vue-router'
 import HomeView from '../views/HomeView.vue'
+import LoginView from '../views/LoginView.vue'
+import SelectProfileView from '../views/SelectProfileView.vue'
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
@@ -10,12 +12,14 @@ const router = createRouter({
       component: HomeView
     },
     {
-      path: '/about',
-      name: 'about',
-      // route level code-splitting
-      // this generates a separate chunk (About.[hash].js) for this route
-      // which is lazy-loaded when the route is visited.
-      component: () => import('../views/AboutView.vue')
+      path: '/login',
+      name: 'login',
+      component: LoginView
+    },
+    {
+      path: '/selectProfile',
+      name: "selectProfile",
+      component: SelectProfileView
     }
   ]
 })
diff --git a/src/stores/authStore.js b/src/stores/authStore.js
new file mode 100644
index 0000000..79696ca
--- /dev/null
+++ b/src/stores/authStore.js
@@ -0,0 +1,32 @@
+import { defineStore } from "pinia";
+export const useAuthStore = defineStore("auth", {
+  state: () => {
+    return {
+      token: "",
+      user: {},
+      profile: {},
+    };
+  },
+  persist: {
+    storage: localStorage
+  },
+  getters: {
+    isLoggedIn() {
+      return this.token.length > 0
+    }
+  },
+  actions: {
+    setToken(token) {
+      this.token = token;
+    },
+    setUser(user) {
+      this.user = user;
+    },
+    logout() {
+      this.$reset();
+    },
+    setProfile(profile) {
+      this.profile = profile;
+    }
+  }
+});
diff --git a/src/stores/counter.js b/src/stores/counter.js
deleted file mode 100644
index b6757ba..0000000
--- a/src/stores/counter.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
-
-export const useCounterStore = defineStore('counter', () => {
-  const count = ref(0)
-  const doubleCount = computed(() => count.value * 2)
-  function increment() {
-    count.value++
-  }
-
-  return { count, doubleCount, increment }
-})
diff --git a/src/util/API.js b/src/util/API.js
new file mode 100644
index 0000000..b932e12
--- /dev/null
+++ b/src/util/API.js
@@ -0,0 +1,106 @@
+import axios from "axios";
+import { useAuthStore } from "@/stores/authStore.js";
+import jwt_decode from "jwt-decode";
+import router from "@/router/index";
+
+export const API = {
+
+    /**
+     * API method to send a login request.
+     * If login succeeds, the logged in User and their token
+     * is saved to the Pinia AuthStore
+     *
+     * @param email email address of the user to log in as
+     * @param password password to log in with
+     * @returns a Result with whether the login attempt succeeded
+     */
+    login: async (request) => {
+        const authStore = useAuthStore();
+        let token;
+
+        return axios.post(
+            `${import.meta.env.VITE_BACKEND_URL}/login`,
+            request,
+          )
+            .then(async (response) => {
+              token = response.data;
+              const id = (jwt_decode(token)).id;
+    
+              return API.getAccount(id, token)
+                .then((user) => {
+                  authStore.setUser(user);
+                  authStore.setToken(token);
+                  return;
+                })
+                .catch(() => {
+                  throw new Error();
+                });
+            })
+            .catch(() => {
+              throw new Error();
+            });
+        },
+
+
+    /**
+     * API method to get a account by their ID
+     * @param id ID number of the account to retrieve
+     * @returns A promise that resolves to a User if the API call succeeds,
+     * or is rejected if the API call fails
+     */
+    getAccount: async (id, token) => {
+        return axios.get(`${import.meta.env.VITE_BACKEND_URL}/account/${id}`, {
+            headers: { Authorization: `Bearer ${token}` },
+          })
+          .then((response) => {
+            return response.data;
+          })
+          .catch(() => {
+            throw new Error("Account not found or not accessible");
+          });
+      },
+
+    // Sends the user into the home page logged in as the profile they clicked on
+    selectProfile: async (id) => {
+        const authStore = useAuthStore()
+        return axios.get(`${import.meta.env.VITE_BACKEND_URL}/profile/${id}`, {
+            headers: { Authorization: `Bearer ${authStore.token}` },
+          })
+          .then((response) => {
+            authStore.setProfile(response.data)
+            router.push("/")
+            
+          })
+          .catch(() => {
+            throw new Error("Profile not found or not accessible")
+          })
+
+
+    },
+
+    // Sends the user into the "register profile" view 
+    addProfile: async () => {
+        console.log("todo");
+    },
+
+    // Returns all profiles to the logged in user
+    getProfiles: async () => {
+        const authStore = useAuthStore();
+        if (!authStore.isLoggedIn) {
+            throw new Error();
+        }
+
+        
+        return axios.get(import.meta.env.VITE_BACKEND_URL + '/profile', {
+            headers: { Authorization: "Bearer " + authStore.token },
+          },
+        )
+          .then(response => {
+
+            console.log(response.data)
+            return response.data
+          }).catch(() => {
+            throw new Error();
+          });
+    }
+}
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
deleted file mode 100644
index 756ad2a..0000000
--- a/src/views/AboutView.vue
+++ /dev/null
@@ -1,15 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>
-
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
-}
-</style>
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
new file mode 100644
index 0000000..1a6a64b
--- /dev/null
+++ b/src/views/LoginView.vue
@@ -0,0 +1,114 @@
+<script>
+  import { API } from '@/util/API.js';
+  import router from '@/router/index.js';
+
+  export default {
+    data() {
+        return {
+            welcomemsg: "Velkommen tilbake",
+            email: "",
+            password: "",
+            errormsg: "",
+        }
+    },
+    methods: {
+      login() {
+        //todo: implement when API is up
+        API.login({email: this.email, password: this.password}).then(() => {
+                    router.push("/selectProfile");
+                })
+                .catch(() => {
+                    this.errormsg = "Kunne ikke logge inn! Sjekk brukernavn og passord, og prøv igjen";
+                });
+      }
+    }
+  }
+
+</script>
+
+<template>
+     <main>
+        <div class="login-container">
+            <img id="logo" src="../components/icons/logo.png" alt="Logo">
+            <h1>{{ welcomemsg }}</h1>
+          <form @submit.prevent="login">
+            <div class="field-container">
+              <label for="email">E-post</label>
+              <input id="email-input" name="email" type="text" v-model="email" />
+            </div>
+            
+            <div class="field-container">
+              <label for="password">Passord</label>
+              <input id="password-input" name="password" type="password" v-model="password" />
+            </div>
+
+            <p id="error-message">{{ errormsg }}</p>
+            <button @click="login" id="login-button">Logg inn</button>
+          </form>
+
+            <p><RouterLink to="/newuser">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p>
+        </div>
+    </main>
+</template>
+
+<style>
+
+.login-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+
+  min-width: 300px;
+  margin-top: 40px;
+}
+
+form {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+
+.field-container {
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+}
+
+input {
+  height: 40px;
+  font-size: 16px;
+  padding-left: 10px;
+}
+
+label {
+  font-size: 18px;
+}
+
+#login-button {
+  background-color: #00663C;
+  color: #FFFFFF;
+  border-radius: 5px;
+  border-style: none;
+  width: 150px;
+  height: 40px;
+  font-size: 18px;
+  font-weight: bold;
+  margin: 20px;
+}
+
+#logo {
+  width: 100px;
+  height: 100px;
+}
+
+@media (min-width: 1024px) {
+  .login-container {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+}
+</style>
diff --git a/src/views/SelectProfileView.vue b/src/views/SelectProfileView.vue
new file mode 100644
index 0000000..6dc0b9f
--- /dev/null
+++ b/src/views/SelectProfileView.vue
@@ -0,0 +1,113 @@
+<script>
+    import { API } from '@/util/API.js';
+
+    export default {
+        data() {
+            return {
+                profiles: []
+            }
+        },
+        methods: {
+            // Sends the user into the home page logged in as the profile they clicked on
+            selectProfile(id) {
+                API.selectProfile(id);
+            },
+            
+            // Sends the user into the "register profile" view 
+            addProfile() {
+                API.addProfile();
+            },
+
+            // Receives all profiles from this user
+            async getProfiles() {
+                await API.getProfiles()
+                    .then(response => {this.profiles = response})
+                    .catch(() => new Error());
+            }
+        },
+
+        mounted() {
+            this.getProfiles();
+        }
+    }
+
+</script>
+
+
+<template>
+    <div class="container">
+        <h1>Hvem bruker appen?</h1>
+
+        <div class="icons">
+            <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon">
+
+                <img v-if="profile.profileImageUrl == ''" src="https://t4.ftcdn.net/jpg/02/15/84/43/360_F_215844325_ttX9YiIIyeaR7Ne6EaLLjMAmy4GvPC69.jpg" alt="profile image">
+                <img v-else :src=profile.profileImageUrl alt="profile image">
+                <p>{{profile.name}}</p>
+            </div>
+            
+        </div>
+
+        <div class="add">
+            <button @click="addProfile">+</button>
+        </div>
+    </div>
+</template>
+
+
+<style scoped>
+
+    .container {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+
+        gap: 20px;
+        min-width: 296px;
+        margin-top: 40px;
+    }
+
+    .icons {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+        justify-content: center;
+        gap: 20px;
+        max-width: 550px;
+
+    }
+
+    .icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+        font-size: 20px;
+    }
+
+    .icon:hover {
+        background-color: #d5d5d5;
+        border-radius: 10%;
+    }
+
+    img {
+        height: 130px;
+        width: 130px;
+        border-radius: 50%;
+    }
+
+ 
+    button {
+        border-radius: 50%;
+        border-style: none;
+        width: 50px;
+        height: 50px;
+        font-size: 50px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding-bottom: 10px;
+    }
+
+</style>
\ No newline at end of file
diff --git a/src/views/__tests__/LoginView.spec.js b/src/views/__tests__/LoginView.spec.js
new file mode 100644
index 0000000..64e4c4d
--- /dev/null
+++ b/src/views/__tests__/LoginView.spec.js
@@ -0,0 +1,16 @@
+import { describe, it, expect } from 'vitest'
+
+import { mount } from '@vue/test-utils'
+import LoginView from '../LoginView.vue'
+
+describe('Login', () => {
+  it('renders properly', () => {
+    const wrapper = mount(LoginView)
+    expect(wrapper.text()).toContain('E-post')
+  })
+
+  it('login button exists', () => {
+    const wrapper = mount(LoginView)
+    wrapper.find('#login-button').exists()
+  })
+})
\ No newline at end of file
diff --git a/src/views/__tests__/SelectProfileView.spec.js b/src/views/__tests__/SelectProfileView.spec.js
new file mode 100644
index 0000000..eb41136
--- /dev/null
+++ b/src/views/__tests__/SelectProfileView.spec.js
@@ -0,0 +1,28 @@
+import { describe, it, expect } from 'vitest'
+
+import { mount } from '@vue/test-utils'
+import SelectProfileView from '../SelectProfileView.vue'
+
+describe('Select profile', () => {
+  it('renders properly', () => {
+    const wrapper = mount(SelectProfileView)
+    expect(wrapper.text()).toContain('Hvem bruker appen?')
+    expect(wrapper.text()).toContain('+')
+  })
+
+  it('loads with one profile', () => {
+    const wrapper = mount(SelectProfileView, {
+        data() {
+          return {
+            profiles: [{
+                id: -1,
+                name: "test",
+                profileImageUrl: "",
+            }]
+          }
+        }
+      })
+    expect(wrapper.text()).toContain("test")
+
+  })
+})
\ No newline at end of file
-- 
GitLab