From 74808fa23ff2f66d95742d46a006ee39e8c4bbbb Mon Sep 17 00:00:00 2001 From: Will McVay Date: Fri, 7 Feb 2020 16:49:51 +0000 Subject: [PATCH 1/3] fix: oath login WIP --- .vscode/launch.json | 7 + .../cognito-auth/src/session/get-session.ts | 7 +- .../src/session/refresh-user-session.ts | 15 ++- .../src/session/remove-session.ts | 6 +- packages/cognito-auth/src/utils/cognito.ts | 25 +++- .../src/assets/images/reapit-connect.png | Bin 0 -> 22433 bytes .../__snapshots__/appointment-detail.tsx.snap | 40 +++++- .../__tests__/appointment-detail.tsx | 110 +++------------- .../appointment-detail/appointment-detail.tsx | 73 +++++------ .../__tests__/__snapshots__/map.tsx.snap | 13 -- .../__tests__/__snapshots__/home.tsx.snap | 14 -- .../__tests__/__snapshots__/login.tsx.snap | 103 +++------------ .../src/components/pages/__tests__/login.tsx | 120 +++--------------- .../geo-diary/src/components/pages/login.tsx | 104 +++------------ .../__snapshots__/appointment-list.tsx.snap | 13 -- .../src/components/ui/appointment-list.tsx | 5 +- packages/geo-diary/src/constants/api.ts | 5 +- packages/geo-diary/src/core/store.ts | 4 + packages/geo-diary/src/reducers/auth.ts | 11 +- .../src/sagas/__stubs__/appointment.ts | 23 +--- .../src/sagas/__stubs__/appointments.ts | 13 -- .../geo-diary/src/sagas/__stubs__/offices.ts | 1 - .../src/sagas/__stubs__/properties.ts | 1 - .../src/sagas/__tests__/appointments.ts | 2 +- .../geo-diary/src/sagas/__tests__/auth.ts | 32 +++-- packages/geo-diary/src/sagas/appointments.ts | 2 +- packages/geo-diary/src/sagas/auth.ts | 14 +- .../geo-diary/src/sagas/next-appointment.ts | 4 +- .../geo-diary/src/styles/pages/login.scss | 13 +- packages/geo-diary/src/types/index.d.ts | 1 + .../src/utils/__stubs__/appointments.ts | 2 - ...stLetter.ts => capitalize-first-letter.ts} | 2 +- .../geo-diary/src/utils/__tests__/login.ts | 19 --- ....ts => sort-appointments-by-start-time.ts} | 2 +- ...stLetter.ts => capitalize-first-letter.ts} | 0 packages/geo-diary/src/utils/form/login.ts | 22 ---- packages/geo-diary/src/utils/session.ts | 3 +- ....ts => sort-appointments-by-start-time.ts} | 0 38 files changed, 241 insertions(+), 590 deletions(-) create mode 100644 packages/geo-diary/src/assets/images/reapit-connect.png rename packages/geo-diary/src/utils/__tests__/{capitalizeFirstLetter.ts => capitalize-first-letter.ts} (73%) delete mode 100644 packages/geo-diary/src/utils/__tests__/login.ts rename packages/geo-diary/src/utils/__tests__/{sortAppoinmentsByStartTime.ts => sort-appointments-by-start-time.ts} (88%) rename packages/geo-diary/src/utils/{capitalizeFirstLetter.ts => capitalize-first-letter.ts} (100%) delete mode 100644 packages/geo-diary/src/utils/form/login.ts rename packages/geo-diary/src/utils/{sortAppoinmentsByStartTime.ts => sort-appointments-by-start-time.ts} (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index e3029a97b4..8c8d4d8c00 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,13 @@ { "version": "0.1.0", "configurations": [ + { + "name": "Attach to Chrome", + "port": 9222, + "request": "attach", + "type": "pwa-chrome", + "webRoot": "${workspaceFolder}" + }, { "type": "browser-preview", "request": "launch", diff --git a/packages/cognito-auth/src/session/get-session.ts b/packages/cognito-auth/src/session/get-session.ts index ca00087a88..31b69b8abe 100644 --- a/packages/cognito-auth/src/session/get-session.ts +++ b/packages/cognito-auth/src/session/get-session.ts @@ -1,10 +1,11 @@ import { LoginSession, RefreshParams } from '../core/types' -import { tokenExpired, getSessionCookie } from '../utils/cognito' +import { tokenExpired, getSessionCookie, COOKIE_SESSION_KEY } from '../utils/cognito' import { setRefreshSession } from './refresh-user-session' export const getSession = async ( loginSession: LoginSession | null, refreshSession: RefreshParams | null, + cookieSessionKey: string = COOKIE_SESSION_KEY, ): Promise => { const sessionExpired = loginSession && tokenExpired(loginSession.accessTokenExpiry) @@ -13,8 +14,8 @@ export const getSession = async ( } try { - const sessionToRefresh = refreshSession || getSessionCookie() - const refreshedSession = sessionToRefresh && (await setRefreshSession(sessionToRefresh)) + const sessionToRefresh = refreshSession || getSessionCookie(cookieSessionKey) + const refreshedSession = sessionToRefresh && (await setRefreshSession(sessionToRefresh, cookieSessionKey)) if (refreshedSession) { return refreshedSession diff --git a/packages/cognito-auth/src/session/refresh-user-session.ts b/packages/cognito-auth/src/session/refresh-user-session.ts index b7e2ba0c09..6f67986e4a 100644 --- a/packages/cognito-auth/src/session/refresh-user-session.ts +++ b/packages/cognito-auth/src/session/refresh-user-session.ts @@ -1,7 +1,7 @@ import errorStrings from '../constants/error-strings' import { tokenRefreshUserSessionService, codeRefreshUserSessionService } from '../services/session/refresh-user-session' import { RefreshParams, LoginSession } from '../core/types' -import { deserializeIdToken, checkHasIdentityId, setSessionCookie } from '../utils/cognito' +import { deserializeIdToken, checkHasIdentityId, setSessionCookie, COOKIE_SESSION_KEY } from '../utils/cognito' export const refreshUserSession = async (params: RefreshParams): Promise | undefined | void> => { const { userName, refreshToken, cognitoClientId, authorizationCode, redirectUri } = params @@ -21,20 +21,23 @@ export const refreshUserSession = async (params: RefreshParams): Promise => { +export const setRefreshSession = async ( + params: RefreshParams, + cookieSessionKey: string = COOKIE_SESSION_KEY, +): Promise => { const { userName, loginType, mode } = params const refreshedSession: Partial | undefined | void = await refreshUserSession(params) const loginIdentity = refreshedSession && deserializeIdToken(refreshedSession) if (loginIdentity && checkHasIdentityId(loginType, loginIdentity)) { const loginSession = { ...refreshedSession, - loginType, - userName, - mode, + loginType: loginType ? loginType : 'CLIENT', + userName: userName ? userName : loginIdentity.email, + mode: mode ? mode : 'WEB', loginIdentity, } as LoginSession - setSessionCookie(loginSession) + setSessionCookie(loginSession, cookieSessionKey) return loginSession } diff --git a/packages/cognito-auth/src/session/remove-session.ts b/packages/cognito-auth/src/session/remove-session.ts index 7a00d98e34..c65256230d 100644 --- a/packages/cognito-auth/src/session/remove-session.ts +++ b/packages/cognito-auth/src/session/remove-session.ts @@ -1,9 +1,9 @@ import { COOKIE_SESSION_KEY } from '../utils/cognito' import hardtack from 'hardtack' -export const removeSession = (): void => { - hardtack.remove(COOKIE_SESSION_KEY, { +export const removeSession = (identifier: string = COOKIE_SESSION_KEY): void => { + hardtack.remove(identifier, { path: '/', - domain: window.location.host, + domain: window.location.hostname, }) } diff --git a/packages/cognito-auth/src/utils/cognito.ts b/packages/cognito-auth/src/utils/cognito.ts index 62a93131c0..ad1b78b8e0 100644 --- a/packages/cognito-auth/src/utils/cognito.ts +++ b/packages/cognito-auth/src/utils/cognito.ts @@ -31,10 +31,10 @@ export const getNewUser = (userName: string, cognitoClientId: string) => { return new CognitoUser(userData) } -export const setSessionCookie = (session: LoginSession): void => { +export const setSessionCookie = (session: LoginSession, identifier: string = COOKIE_SESSION_KEY): void => { const { userName, refreshToken, loginType, mode } = session hardtack.set( - COOKIE_SESSION_KEY, + identifier, JSON.stringify({ refreshToken, loginType, @@ -43,16 +43,16 @@ export const setSessionCookie = (session: LoginSession): void => { }), { path: '/', - domain: window.location.host, + domain: window.location.hostname, expires: COOKIE_EXPIRY, samesite: 'lax', }, ) } -export const getSessionCookie = (): RefreshParams | null => { +export const getSessionCookie = (identifier: string = COOKIE_SESSION_KEY): RefreshParams | null => { try { - const session = hardtack.get(COOKIE_SESSION_KEY) + const session = hardtack.get(identifier) if (session) { return JSON.parse(session) as RefreshParams } @@ -119,6 +119,17 @@ export const checkHasIdentityId = (loginType: LoginType, loginIdentity: LoginIde export const redirectToOAuth = (congitoClientId: string, redirectUri: string = window.location.origin): void => { window.location.href = - `${process.env.COGNITO_OAUTH_URL}/authorize?response` + - `_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}` + `${process.env.COGNITO_OAUTH_URL}/authorize?` + + `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}` +} + +export const redirectToLogin = (congitoClientId: string, redirectUri: string = window.location.origin): void => { + window.location.href = + `${process.env.COGNITO_OAUTH_URL}/login?` + + `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}` +} + +export const redirectToLogout = (congitoClientId: string, redirectUri: string = window.location.origin): void => { + window.location.href = + `${process.env.COGNITO_OAUTH_URL}/logout?` + `client_id=${congitoClientId}&logout_uri=${redirectUri}` } diff --git a/packages/geo-diary/src/assets/images/reapit-connect.png b/packages/geo-diary/src/assets/images/reapit-connect.png new file mode 100644 index 0000000000000000000000000000000000000000..207e7c5599305f3994bc2b0bf3de445c1c5c05c6 GIT binary patch literal 22433 zcmdSBbySpJ+cu0SASj>+h=7!IcbAkj5;Jsn!_bX^3P^W%4Gi6_h~$7YNJ)2>^t<`p z_x*hDv)1=~Ykhyc>*b$eX0B^rJI;Nc=W(3Jp5V92Z?Lh5u~1M@u;pZ>)KF0FN1&kG zd;1U-d=hyz6AE5lxJqlesykY^dKkmZQC^!lnwV3_*&AD$tC<^{c{%r(3xm68tu?e< zwUiVEO&#r7jqk2u^|W^accY*Pi+MU3o7$SYQka-qT04kP?KQPgQCORaP-*fgu`4-A znp;`Rdc(}sy_Gdgy=_ee%&5dfDTF-*K?C;YuErFe_I3^~f}SE&|FkOz{=fT}jf&!* zOI&S5sQ%R{Ev2^i`5Zk$;KfdAi&1X$;Qda z09aCEf$&n&*Ra&&ccv2t{xkd(ZeH315GO=|};M-LZbtliDcWMGc=6#ooc(E5Mwi`4(v-oNiP z`yczlE&V^<%LY2bcDK0y+hYIcBVa*y|NfWhf;azV{N@h8;$gtFy;#_DP*60U%1OP} z@cg|!i{Xhs_8tAtPD*9dMwrG!nq#~W)b|o76?!4~_XwolKF$3?A*=qd@F^wLlVqc} z$r@~50`FM|e89+lWT-{DpV>8bw^^pZEWM$%Wi~hVrkM|Nhng^;e`7agQD{1vTv^WHa6klIVvd z!7ojH5(sW??16xtE}4&ACXw6Sq_jV*Mhxd&IBL+$Gi~|d-d!wPOq-uyXs&rnmkzZp zq^Uq_qj^<+cP#}XS;L;4#g0gE4Bz$~QvII~m6!OghiL9@MX??;8^z!1v#d!UuW8BEd76$A`%)_pd-m*+sool(D>l)DhdSg=j7~GJ8xZU{q|1! zdv~9Cw{N2~s_|ApAhuI^npi!2OFE{DcV7h3iciCp;nq@Pf)_P?|LWU!F7~mrSpVg_ zH5S(iy8X88axLHQdiOT4x))#aYf?y3>D|qd&=*Ga=Y{Me1b6R-8Ci($*9Ds7Gu^$* zgn{=G9b}DM#y_YRkvgn^aX2kwfbN3W+ow1~YX`$j#o@W>o{~o@mw~I}ev7Rfd4WaHxmba8ezev2pnR&N;@x_O&MP)Hl{~ddE!sI<{Je^) zssveT9!D}V+)FeR;sl*nGy($JjB)t|1))+@Vq$HCEIJR+(Q6&&zs=2&K^_EFUo01T zZ>ygBB@Ju-U}7!Fp0um#FTUNtqtH~_Lz7EB;?OZLAR|0QjulV9i;MY5pUq?cvf?(W znC$6E@|9HPDlKL8ZEf%B>YAPHn4ix`{n*{MXo`mNrC77<>U7%i_Uf6WgiS{P0alQR z&nYp7$&bm3rFuP9_@m6HNC!m}?9Pj`SLZy|G!e*XsMUZ41AG+!v;Fw=l`~zJN|PYZ zw5jXfTuVlP=zKq)8pRV?Y8;b_?y)h1{d8@M&)LRoQ(a05$+?KAIZ84~p4tntR)>kw z?&#=f%bSBoY>%~SY}~}fQ8uwrtZP0s^1=-gpySuwOXY)R^qhSVz1jxH^JC@kqhwnn z{RUWPt-UUbLzS1XL6iH=#mSb6vh*^Eii(Qw3<@_!US3{?-|0qA44s?dkJ(wj>Dr7& zkG=5U_pNrVYfA68FBQr=O_m&Uj&*Kh@lK=GwoRdrj}>d$&ygAru~E{~fB7-~+|akk zu+4vc2g|6Fk%?(-@OJxbK7!Zn+c;u+`*%%WSJ!j5hriysym%%qSrLBgs9`=P%+YaJ zxh8-qK&&9U{q&%+#<`Q}*swRHL}l?J2|?4Vvhf z7+$--PhX*XlBoThF}$UY-$ONN@CXd!woPQ?1tMT*PI zQ6Tq>f)kSoDjFM!<1me`t!osE1R~Ykt1{)!q(_N+Rr>2zDp__rvvG@g1pKci`;)lb zlCIEE`mxnNB=FT?(%y45w!a^jP|W>VtuI<3+5JtNFYo03WUI+dOSVmV9@<}}1J26p zqvV6pl5uLmi{ax9a{J%-3+wA&exsCA_=?_NQ|C>wH#L>4deWem9viEntzC3UwY%%^ z?j54y**)tqqD>u$y28oE)f-M5TTC`>D1tKMdHm`3SBthXyHZ9qI0>x!SL->re2#PP z+p&yZqoShnlZMOWWT`()=*vqY=HeVqU^65mIMIxWhq<_+2eH5jEKC|V!K5xOdg z?5ca6pK)GZTpjr-xpT5G#mFDs&3&8~%v=crMJC(kC69Cs?9cT6B%#R1ZrWqx8bVCW z1KnCX84(n%2doK~2VGp=C!2__%DK6@D$Bu@1I5#IF32OHry!_7OW`l>1^QzIUU1|0 zskS>;E$CeMOOuaZbYqHBJlRha@8vlBxi@(*0(>IqJ@fkyA3Eyh{iiF<`)FCrO-+wh z5)5k`=jWEm<0d~C<*zwEn%%9mLEe+mh1}j=M0aAr@XdN4@vXKTC6Jd{Z}|gm-n;L- zC-VA{-m#zoh2j$uLZQ(7{QT9`)wu>2Av(G)r|B}aqVL~ZXB&Y0e?DSN-0cI}S{ba;}S6cP_d>s>n-2Nyg9t`YfkgZ{OD0bIae2QUKZF>)* z`aY8vS_bc4d+*W#@VxUCrf($il{sxyRaIZV{#IkF8Szrc{mkJ#C7C+E&*@`<9c*Im zhvZr)gp};;P9yn9pVRHRW&sJtIME%PCIn(ir zDr*A{HPH_ftS%|PQ|6q?C2C;Q)YOzxnxOqm{Q;fIjn~ETMo9_t;cE>wweJ=#dU~le zc%Kbgyu*lj8ugppSF4b|qU1t2|70gPD7PPwr`1aWSo~&gdf}ZIP*+U$Cjfs z+v_4Ql<==t4dvyLMbU#9Vjf%L$UlG53(Zg-QANqh8qpYH1!dRQ^Rux<=(${+p9=^i zEn#rVl%rOe%c*F`y{H7PmJxf!uEXBAt17wZBl}1kM?+K7U=o+d*>20A*sZX-d3=0) zx_}G1+asf_?d|R6mKLAm^&v3HM_=I&Fh?fU1FPi~8lrdYAW9MyTCFDO*a@aQ_q~l` zQC@BR%IX+Miz;hA#8O8~7kCEI(D(>4@%ySp8ifx)24#q}gDB!cPrmXYJ{?xC31A6~ zbE^wkG;(qZ2{rN9j^mJvrbZ3#&NiLx8#CGvpgevcvUJZH5@0^|6$u?1g=5C<3yjya zag4-81xsb`wHZz~d63J<3wi94!tqfa=a!d0(bohwkn_AmAxowJe%TUh|Jl*@Us9%& znfv;vU&*vOGXib|&e^WBSq+*4FCL%(0|<$Nf_pLwFH)R^P^_Lh39!%z=l%Rmn-eN- z#8V2^sqOl7`nXR{R#tohg2GP33kQj0N`6UgnT>o=Pc~z4At~<)-6DuQ&;(5m`M`>n zqXDyuM291|T2Zs_Ik^Tc3K8`aCw@C#R3-||826 zERk8>*%2}MA)6dz#hv{3ujk#35;8J^`BkV#X5m%$V@W(~8E2!uFfLc>PP&Y&*!2?c zMMbv5v$QhYI)2>y$!^yD%kT1ZnDo`^LdWMqxlkEafk`{CyGU^`Y7Xj^W$k0*5RHS00P5fTy>j%{9D`36a-K4+8t{Rh^No^IePIyg3l z?CI$l8mbW3-rC-_Q=_DzshpY7%Ttr{FS!&s7vVgF9%~sK9xZ9;0Ijkb%hqMFmTm%8tXuN1U#lI5V#7fT22O#1vEjr9LdGHAdeO|_GXx7W3 zr@NJkE{0xKNKsKPjLjLC+1fG@hBh}h3%YMh!L#2)7~vTpjJ~`%^d1=*d4{+63=a?Z zhoeK7d1W$I5gbnaPVo%3y|dG40+Mkqxl`b|wnx?)MEx!%r>3+`G$mlO60ADA^D2t+ z^2$m|a*EAw!e7AFH&t0q#>iw(Sy@?^S)LFP{RM%<=WKUrYpZYRU>~!iouR&@x)RS6 z6=m3F3CYFStMLIMvZNuU`VQwRH+s`1Y)q8Bfx$oqc21Y}5{=Zgf)5|BulqdCf)eJX z?ch)(@_oJW0DXb<+ z_2*l!FV6Pn;Si%xLKbE#M_>feSPj1C86Vw z6q}w2kSaa_5hd*6T)t6b`3J%N*w`w87DEUPp_9(!kzDkuj$?f2mxLPpqc?SU3;M?V zH#axj3KRvm!^3!{4lWSL7?=rG!`7>_`2ac^5hJ)3%)7L(upVMNk{_09f&Q3ivdP0S zS^z91(j9koWhLsPeC_$>7*n0&JnwqrW-^`J%0yVz<@ZPTWmg`*6#Owg-Q~;p(QIqF zZfSSEO=ZzqZw3LP!YfA<0a10e=$;9HFN9pz**%@QL)lsjboCOyWw5H0R|b7k&!|6C>lxoX7v$UbNkxSfhz8JDzuIz+{u>Y&#fL`8LPc* zV-U6ZK8J0<6~%HObrOx^e_9 z7n{X<&0JGRWNg?M>bP_fpHFs!9iMU4)ztdU{Lh0-j%F~(eT}aAlQS~T2E{nM z+5C5B=F!m9MhwEPu8x#j5@_DJZBII9WfkvhkZQT;K;q)_4eH~)SmuVhipSd1SsH8V z-0)qzgxzYQvpoXBMDf)0?F0Z2h3+1ULrKCI~A@SM&^X=nDPgrE)T9YDeOq!tE;rE~;05wv$ zER~InY;zEe)zz^B58pDz=^)0-nW4ax_S(qPxUJQ?!qfJxaiJ;D24PW^ZZ)Pcw4#mk zGje&I`ty_B-`Tp63Yl#4<1!F!^Y89Z?9R%CNQ1q;wia_i z>9biixO%oVG5?TZIYKNv<~P3!ZLgZeD)qEnw7SNqA-Ny-_4V1jrP(Hc3hyhyJpb<5$Ez7$-ax zev?(1E02^Wp6scIkH0x@7f(q5K%DS&Crc6onTs(1ELiO3Z%&127c%86De4Wg zpzq&bb1gn5rl!bqr$+zli)w520;Nl%)qa)76WWndV|%TSrW@TtX~U`0A>SOtuU5SaVA>`XF};DHpoidul`nG;F7Tyvy57CgFTnRzJOd`SNN%I zDk1TG+EL|VyUNRg46@a|a_F`-K9Is!mnm+O2{ALv)wR;_W2U9GI9#qf@&Ahn(u0f| zKVjDS@lR^D@|>_e(s03=?Jg!EoV6LRxB99K~E&U#y-eKdQzGu`hiR1Al=UTpSlZf;tSh_e4;Jl&l= zn)NUyY$SJ_Z9KWT+_Rq{%9`Jo4C_WBE0S}rKa+p&30Om3K6dm)=`%6zP%NLaOIJc_ zxhC=9mEOX(x9g@$w}thh;}mX!`qrGka;C{;-Y2&&&Lnr)LKCO@lP#T*5}<*|Q7dY_ z@je0E1>f6q28JF&KiAIA&M0w1ZS9_Yx$liKuc+R+?fl+5&<4bm&DZ|kx*B5_fjoh8 zIn~?L8=Iw|ef<{`i%bggJ$ntP9XLwc=7TOzoVaC6ez``FiR5y%V^iVLBHHBk_pTfX z2$#y_4Ya!Lnfxt1n!oj3>o%-BCT$Iiv`wTllasAkf$7M5+Wdf6HGcn2m2B#M=&6^V zFBS1+(ri6F^OLyhXfls&cJ_@)^RlL)pZ)PVAM-WQG}xv43zD8?JPu1gu>-E0N&k7? z@?`JQzE@Dui-~~iv!MO%btNCzB6%c*?osCor%)z(inQ0%(e_X?mXgQb-0bG2jUdgJ zPCPa?whLX@=BQj>3pry#g5N=Bc6BwkMQBY;jeeyW=Jk}bk^&ffkY7r4^!qy9S8zC- zknQIEx1^8vP^=-EdiM;CEOp~`BVjk2U@{9d?c`fLb1HLVkt>IG9~I4^Je{zr1@9>s z1b>l{d*{K6=g;2=sZP~5G>kAW59Pe+i)Vo_vUq0C&dkgViW#>5jkzo>bvRl@)*ZiM z5fbtNV8yeG2Sib;Z|~2S^@HJnR^?H$SiwXi&V#elTxTGCa-fc3LQZPlY;heche_Qf z)Y-79Z9=N_$PO7jv{`ct3qS~^SWt?JigH_aP{p(Ar^c8b;CgrckRmZ5{T21 zV7x4i7P0c?vsOyK>eGZgbS|`>pdjxJZWg(RZkP^YQi`NE31#FG>2U z5h_mc$EjW-D=<$4n+&3_uf!Ol5w3&0qU!;vjj2iY<>`)P{u&vX3OzooK~L21t?nAx zN2rJse=5LzAe&c{CLkdAH2L*C>c;OH+ljKsdItu=Ev7et zvrkMb4>1b|-SyZQt^sDe+@|6yC6}j0Lp#^xyoBDPf{BTll%zNi<_aRW$6V43$dD|~ zCcZp<1;CrrpLRzj{kPP3O-)T+UNu%zmFC4zgiiJT>A^viEOl#ZEB4WoU=!d#a_Wb!qNsSZDc(}iBwYdm@;jC@T*_;nGGD9q2H$v>D z9!3*igv_D00h#Y~-R(ye#tTwXQkItI*q|8Nw80koj0_gZHlLM-FZcw6m8@58TSuIjZTZr7`(Yz_(D32OeA?=ahW?KEe+Wn z>b;SdV9}pAmGj0)9V5VgB%kG+n8)TA(CQNPgDrX|_%RIp1e zGmy5+j;U#BpT|5K^qVN6JFIYm6gXMWsU}d?2GTB9Q^t;UzsZe?0_p@zfRg7G$bkZ8 z8=dm;T#9va-n6|&y#{10`xySqXV20S6J^zSM9VyDj&HX_S`QF?qD-LmujQ;H86EWft*i)F!qzl(FqiC|`0s|El zZyNsr#x+ce%6V_j@7SOj4&R(G49IJHVN#Kp8ad~8CL(x!m71DLtC;w!EkG=KX+twy zk5vrVHz0DkTK8lb1>H7B4Fl2~{;GgvllD1oj!YQL+1cDZ4*+duJ?HYyXYp4Q_* zr?tThz8_UD%{sb!rja`VI`)@m0SlLV6E_E4!A0r?9ApskM-LxP);p_%oop3YE_I>Ac;Ar%# zRxc5{bzv`s1|A~f=H^5d;+gZbW##27!}T3mMz_jtZ9780jY`)D~X6^RysiWfKZw? z3}{I%iV7C=*cEpA6Y9-Y`j-CYe2qMLn%#DM`}GI(7uk)3ta{u4Knl_TLe`+grtsT7 zos#z!D5y1g>H@KW6y(&yfAyhMblebgnT?4_hyDa_bmZBL%U`olQG0* ziNaGW`$zrDc6h^q_X;S}fX!y?B=ZLL?ik?45llJio|V<%j42#vSc2O4ISQr8}SM1{HMa>vF* zE;)1|88uyo+HLX^$k4@9=Gy`?QW*f%aebtssIC2?D#&B~*MZ?J@#}&I(VFW&UgF)8 zvw%4{vFo9Mf@nIo^>94P#h^n*bTo4e0ZHkrqM~YtS&Qz-EZ~@R_waV-T8cFogCw#D z+PZou1$|ESlC4hwj$VEup@Sx_Q)5I*rqaFyiSH)=Iny7~#CGaxJyq9axL3-njQ1`q z6A?}a zyfI=e69&v(j;3`kg%$#|iij1U`{L0Rj*VYzrdvmRlE?hLf$eF|pXkQQo$2}gO?M$;#P>Am> zXvWE}(7VeI6j z(lWwWY(X``TrB)ax7zA;ws2YxYWGUWSOElz%+=mWOG^t42{~p3tR-4T5{kt`Q`BFf z2s_?EXkjVTLa!ywW4rn|LwESn8e+a;#~X^I2_}4bKQKN*GOqdu77*xc;0Q9tjjzLTsi}5|HxgUp_Up{bM=yjFhTxE5A#crfWZv+ z_bxaAa)}_XIdwa_dZwSr!(}nL_b3Dck;h5sTgZ-nO-@NEnXhu2cvGKFjTpAx#(ijI zZE0zlny|gLwzmln=u%How8(t7uEYuo%mY#4YcGTznb_KrJQ3gOm1j*Du}($I2Xg5cTj*@x$72n6jNpIcMr=+BWg@rjA z)HF2-rA1+Ya$gehpYa(_PtV~b`gMQ-kjr6`maTkH`>w37UmyG40R4gv3-)JILpA2Y z(vlZ10?pLP>frSy>#ubNkUvuEF+#rML1#U+Nc*W#27hHqekKpN8k(9)iW;^Wpxanj zc)hmk8zW1N=i}qk@|=y4v3k6Eb7e)^l8rvD*Wz&Q;OwkGU6mSPVL^pJO?)YA-br06 zvy+$+w>Mwvu!sV+Cr5!u=jo|xryDXqnGSB5k=j)j&I&{>S&b4ClKp&-{=3$`$^ZHj zV2!`(M-P(_rrZB;p7In<*J92;kVm*H;w0lbRHh*RcrU+yLyy0iHo!w1CXv-%@C1|- z-MS$0g}H^|+Ib6@H)6r#7HIN0MT}9J1PMLEc2c_o?heYe;fu-N%13PoshF-b!GeQ) zpw4<^+y;ino?Tx4Hcu^!EWKxJOo_gF$Y<05ij>tt)p!-y@LB~$W^l_6rwp#9Ms3bx zw^a0kHW0Z8(;$FU#`dS`;=&we6Y^Hn;K?tZPPyG;A$36#;C~}9T$0V07~J3gb=8G0 z0|%I&;A}}xjkdQpG5l%!Os0L@#@{m4Q1fB6=WN&sN6RGF)NzMivGRG{OltV?-NP>1 zvuQL2rM^NWI1vig+_C6N+Q=ai$FJIrt}oR6Am6IFiNh*oYIG1qfU39680hPZlh5O+ zGt$D(7<8OqT|byVvfbw=EfuHmTpZDm$coe($xgdWvJqn4GE~U@gw4nB@=iLOF}EOwJU{=(m>0 zWD)b;%R;O^h=DS1v6b+@VCzkAI% zx#pea^A16#|0bL?JiJwkDnAP&h7BHc_6STbYR;scfm@swEz9Y??FCLzPd>aCUTJ5o zt(+BBrM0*r6J6Xs7vE`Ntzk1-O5o(-K`!Vz`G#ES?C6N=79rxi7y-(F#wB$))2X^o z0L~mtyY!iF^*h_0tr>p@BuSw7rD_z1zuG&~!f~EJi{rHtr>RrtAZbY?%N`75FTZ{fI6FHR0zFnPgh{GmvUY`r z7p9#z8PzHKoDZkTJy#|ko(-oLkc>~a-P@IF-A;Y!iTX|kA!EVAq^zvGu&@Qtbtt@C z3P9+mPgA-zArJza`*$)q`4Ps?uL|MiOiZQtLL7Ro$W$%6FnLIPlxDoT(H4EJiyOXI z{ljg$n~`r%D#ji!udW)j`D0s;;l;(q_GgIgE%(IyyITcCUgL~qqn+xVb;hcSV#Fu& z=V**rW(1viJW2=$8=ngT=uJqGLE=Lf_6@%jc@9Dpp+Q7%?b4zGj($ajvxe zY-0^Th{W7hPfp*XdHvmAX!gng5+?%4y*u9PnjU9DhO#ayp*PK53*Bb7q_2cIz7gKY z{i=+cYyPM9_MaVA*C&sp-FZ5yc`xrh_$aY3oYx1mPjk7ka;@uSI(cd=hOO0r9tTA^ zPa&<3-@m_)5WV;V;Q!qyj=sGnAWHtgonrpcLK#n2fQQQzLKwT;NwYGe7(1tch>fcd zu;cyQVKb-B3nxiJJ(J5<^vWpe$pg6!v{dh1YXsqI)`Y<{Aq-U@va_!78gk-4HRntT z;fx8F*(`hc^5qML9YBbK zZSb(h=H`3H1Yjg^PVU5mzpvnlY6@6KT0hh5T4<{d>o;xUvNQSlcKv^@cuwV{!Yg6X z5CLIUlB5&u_wV0ZAd`{(1feojHeWk;l6h7R~k?ZlXL$N?Y4)v)9pTzj3}o zI~XZgc@E#dP8ucefD=3PRJu`AP*DMDt4zw;H`I6nf`Y-F*_oLXqJ~eNJQ4SQ6(TJFF9xB=bxONeMw;Z z(C+9F+DddW&yFoz@^af;a0JZ>e zOZ08@{so|E53$`jfQx)0|o zY7P?M-PtzI8X6$<0e+L5-AMdwSLC?<^chwVz?zhXCjmv!VxU6;v)u$+!Rjx445r9P zoS0QbE!OZ4K_=ltN_r?HxVf>K)CW%a{P{#@XhJhUk&FcQcNV)M$UzmdjI5zo z!@&0-k47Or`4Wv%I;yas>pk%Drptm-%!Dc#HwPavpSd~8jXh21BM6q--Q9KbL*Ms* z06Zc)MYzt!+gQzIg7)lDi#q4+->=JlJ^RCO0RVl=S-#zLZCuoF>m_RmKqtVtRDPLI z9*!2LC?6N`0p#>W5D1wLQ+hlGX#g_(hdhS^iLna1fO6O(M~_&-})&!1LIWvUWY zgiv~^N>sw1f;HkmO~3^G2!voc#<$9p-VB4)nHiLMwR zv+U&S>kIZN*rr5aX^W*QB?y0pR4Rf>m0|fGmwb8Mwwuoi39O(2dLEvdl9I2=m~1WF z>tTF9Gii|+b@?oT<0!*K#Av_^HRSNyHC0j>dtrutScQZFOg0G+I)@rav~+6iw4qQ! zn=uVe&S9{K#|EigOMvCY8M6X~AG5JB2zN66B&jd|eF_BcS#|H~LpY-pO^~-ryyZU#j zzL2i2Zu6`D-s{U?*WY~&dZh^onp|xkJ5(RY7Qf{xi&OGSvQPSIvBt=r?&)ABO){nnn1la5zW;R74={f}H7us8sCfJdTa4IB9YDMdW&=Qt2fS4)2LAQ4JUi3SP=b|c?PQTUokH0I)|RJu zF)=Y*TwKn~_l6lqxi_33jB>_gp}%<7gi~jjs&c^tA9tTsjKg^vXd`Y0swlS`;phpd274@E_cQ~Nby*}jTh?YXA}sD zm%XjIk(%1!?KVRegXtZk58$u{-<4j1hK7bu{66Ow!`}VpKurj=6CB1ObksivGj69Y z_u2rjq>~q2G4&@kO@E#F2Dqu@jvCYtIHZ5WNDE!%pM-Yq{I01v_I7e|T0S~=mFM@r z;zzR10#k=dbSz*o_3bExvX{jLWe|}{NJ=Jc;zq_E|Cx4dGxmL@udmOiSg3ZKe`i4n)D$?lxO}kLxn?hy!(|Up@ln&#`ujHlFuy>DDEw+ACPpw#*`NBy z?O|W(;UTQ~5pgH)@so6$8t*YUU;^ZCTkOyzqtPBb;1?L$gqIFk0d>0rVi1Zt%*cK4 z>|I|C8xtWkmJ*@U2!zTJ-*z3An^O`KySE8J)pL4oF5QAMDoQNI$s(0eNkQRqvzQII zPoINDG(-Tru#n>Rhin37KtMp0tW?2+%7R*l+4ptrTuboReTKkaj1;Jh+-X|RHpiy1 zY^Fgm0*M5Y8^8rFEZ91)gD#Bve^F0PwnVVkrv<$VUMntxM#emNl}7-?G~7S!K6e9g zA8@?fj{wz1MN;c?R)&t~2Nhosv6NH!7YZ_J#q2f8mX~vZ7%peX%Bo-t<$}_`FCO`+ z^jRq3vu7<{E*}@rr7sS;P5?y^fLaDT*tc)r##Pt8y5`T<*gl_f<_Cvs2(*ZC-Y_%$ zfoPiKqbL9+%Jlisb9RUh(85NK5zPpbkfc0)B{R%c(AelblpU0n#k4W+W>3$>WjbAp z_<(^+WzF;IRm*|cTw4J7YnLzw2L~Y4;eMn_kDsdybiG!;bJUToi7JyOr=^TS)1@wAUHm_qi5Uc;5 z54csjg`%Up6xMhbtQuQipE)_A(y@@O5NK}bRX%k`w*!6$4BCXc!|!A);qvTO=BVMM zOQ*)>vW^5eF!1dI1}LhD!-KwI1N~NWXlU`6FCpN}K)pS#=pY5~ zr1~x1LDYYMurTIK2)VEif-MWr(nC1DJ53PmxeDQGQ-K<uGY3{gKI zkREIG!>G_QGc|qnh!Sy?{&suF6X zQ)eqJbEC-0b#b~gfzb7+eD3SpEJxtn(A>yK_2y|r*Q?JMMdXL`fK&m6HXyx@{P6jJ z!@&4hAm0JkD8!vJF`OFcYF^}1`$AT}e&i5DhBB6-SpZY48SO_&@HK!D`h`Gyjnt>p|^JMc*%*N%! zA*nj=f5{@31dbrQ@T@!XR4-5#@z~wV&ZfHGa@yP5J36WX-4Ry#S!AE5)&RCJs0(af zH;)1q84zeucJ}pQ%=D3)bL4eXy=9Oy5xI(=+8gcf?%$r2O(4oRN_bfvKE6v)P zC|ig`M_*VV8_Ag@O|O)vW)km!yMa*nce9v4QN&PBORL;zVGRO-e2pD1QeWE`5v#4l zqU*8P7|!$7R1GDZ1AyY)Qtbffv7x(5S-FT6M2r=04W;6(eNj2{qF{suAoQ# zkF7xMOS9}I`G!B|j*QpYGncdPPrGDLf93m5nn>gV`;fL~cY@z`+>W~p1RC~!)#RXZ z8y;%Bp2bcDP~azGKvY*B_p!B^Z6*Z=1_rM5C5&YJ?D2Ko8sEuFKp*O71zBB}^R(hI z;O4oxV@-GN>w^-B@!@jM0ihKBN$glD{c3kDgROdf6X;pexGZ<`m7W5n@Ua5O zxUMd?96@d!yfhx-2I{OGpTfo`o~>HT(i>J;u)K170zgaQqY~z(N|V-XQe-jo!{8d5 ze&g3~D4tWbca1WbXJ^#ummtmtPE;sft(bq>~{@oG_+xm%QD2t z=VPS`y8Q;{F5cYoHgaGFeFrsm$J?9x_t77lHSdE15_YqVh`Vx#>l2e`4=QRiS*ahZ z06=ngOn{y3bjbVs;RF81DAoi-m?g||#;m%*StTXCpu7hv$L$){AS;||bbIt6h0kWR zP`}9^Xqf7xH7r1GrIbR|dSR27lauofI+U+84LnQhY4XWq;`#p1LKF-;OA(fKljRix z^G|@7b{S}t5F1nSz_3U1AiNW$iWWKa9dDsfi*N6Pz_B`Wcr{Q(0W@aebOgdprTz5j zRMnbO!|L>-&gp4O;GRL?>H`}UyM@)m;|vfDdJUYT;5-Btju<+IdKLe>ckh-DEf&5@ zD`NjL9(s53nS4CPl!Uzd&ZL~^dnIYKfUGEitySy$Xj(lGD1rYpw6wba5Jqe9I?h=F z*9em5HKe^6In4k1>L>_MpkP-e50R~`9(cykwfX#M|2ARZlU9_}qqzTcaG@Chqq5cu z9WLLIs5+9V4{g`(k>BWEVo&S5e&H)9sYVbL)l}lC49+K@nHpC~cdGfvp(2ZAE+6oP z1JKrTx$Ezg~L08&ZR{WS~diXA1#D3s;WX`Vj6tu zRWd|H1O){tDbu5dOZ9?Tn3$f0COO)Y3c1hpvEhmV@R`J=8gT9Qo7#;Bs0xfb49Ems z69s1KNx7}q&w;pL8B|tpu8yRI-!_tj zfs=Wj2f^|>x!sarj)|}LDngTpe@ecZ*MAuO&Np{=x~{IiUaPGW5O z+l8D6QCT^;EW>0_I9ou^prZ6#{1FT)gROo-K$8+qF67=^Uf%x8Q4Nm=FmDEaCpADc z3Q8+7)Obl_S+c`i=NIca8*2mCe&F=Vj~_maaSUa2Q+B){o>SV$Y1BcY}FZ-C2QUuW4-U-Ta(n0)5hNJuX`OSMSg^~L|oLEYe zIHeV61iFo{wKd;?0qXMjn#E?-Mj0 zI0br(C3utNUu!;agLe7End<4i4cOjKDs)TzP3!e+~@Hb#^9EkDl%bfkU((KKeutKlc6@ zW7~=LXmzXQ@7EVxTeMj~#S9=At)?j0@dJZ{eS?DqZT8FFKSxGFHNJy$R2bOAb~w1r zF|jh)0u9lCG9S7Y$%fj3BZ7chl2%W<_vq21yK_oce}RAyfZH1o{{SiC0)uroATl$>yPz^KPEgu8*mnXpadLF@%5ohb6JU~^ zmFT`>R53kxc#A+`ekVqci_?DlR@NrJy1F_$TcQos9_YaB?d1sMs7*la93+{ChS3}M z@9W0Ek4{fHR*Q3zpam+oo^zOiU_v4~R3>4B*31X!D`Pp`oB~ z9HOA0@DOhRFxBaQqa}(jR7(iyrd@@bNzf$iI zCKE@rAwdWXCM{p8K+^D13daam5`Eoi5hK~i(@-dmhWf!1{C>hO+=MbzQd-=;U{3FAGK zn>I=m$lbwqz&QMGFL#p#{{H>)-@f|yh5yH{cx+TPRd`3TgWnL4%%ax`O^A=r#sL~x zP-J-A`Sa(efknnREVqaM^wWqQ7_QAkX_|kZJasgsX+uK`OQfl28EqZ42xLvq{Aw+@& zEY4V@d>$ag!pX%-2~;wyul`~JvE0r?SsRe^!=L`syFlXuAi0c+0-1fD_P}RyWpWOa z2Rga{1%TlsQpU-5eE+__HhS?hB7@!Q(98@9axpM49iV0Bv>m4lyA6woaBTCx-nz{< zRvD+$DK}b(Wl#qh>f6D8*@05dTl#o5!<%Z_F+hg^^&2?EZ@)I6pH9dJ)LOB?2PY=h zTMq65gjNt&<)H5g&h#^q{?j*cO=>GZ%&h^`iG3^Amh!ivA_2tL;~(2wOG`}-vyGyD z>5?@c%ny!^if&E4y=j5%q69`|Zvv$taqTBi60jRcZEzJ#0ec9L?+#OVs<#PX#sH}P za05&?XoKzqr3i6|?3#6CJ&K*0!&*%>7!Qr}Otkp`e2nyM6 z=|N6cNB{und>AiHv-BvK`4|M;sGv>9(U28Za>ZOCcNf^_p;`umWu zman^TqlSNMrM)maI~!EOCagf&%X*A7lMYO%7J86Gh24}I!VruL8?@9&i9kgOH0KA^ z7pTYBuU}*9s#r!R|1`iHsKJPhvukk62L=NQu_4L#tUms)TCP2w>AjDmd!low92Cj4 zQ`;U;5(&Al6dkqGu%aPy$t8xg6;i8o%$VHg7IF;>iE$3G=QHQL zp1+=dpS@nc?VsPa-}n3dT;89{`xCmj`J>0r@Rh$;7g~%Mu}_1BO)WVez^ErCCK3n) zkOKwXz1`Nkcyn`#d-ilf)B?qJRBt})@N@re?)DZc)ep62=V>b|tFW*o2nirdtw44` zLsJ?O`3d}U7>_&f(9Jq)>8%Tc;+k~oEsNFP+V8%;vE+RZ1BX8GvKuh>dtR*}#rxKw zt+}Vinbx)|Yocy<;qp`m5`i$A>zZoM-KVH1fW#-S5`)k>?p$F^7JbX{LSyTk#d-|Q2&Sgq8?)UYvd@^vE+Ry>*JUlsm|UB zbbkGsx)U!m;CnkqAYMTJ=W~x{GiM{Ji|rT}uOw|@<2xd{u*D(k-~JwJw`86=ghE}fUBXZfr#u`gH56?P1pC1m zP$cdz7mU73q;QBCn5_FJuh=N(c!3u4iP5-={go;sz!2hb<5BIGFV!VTcz6uy&u33Z z`k`MLAjX6OKR@zkYLzVFqmT)gxFzM@tEE1F2!T5~p3dsB*_a(WuE;M*A2hL_3yTT1 zu*%fS87954jhev?IP*T?r54pDm&9U#+ASh7p|ILeUA-PCxnIwBV!_ftEe(o5v5Res z5DDtfCj>`Fe}ggzB+|l1;;hp*4<|nVD33S89!p$*8Oe`6A7e=K90y$rtyQ{sIKxiH z(WiOEtR$&Mo(J}bh$C88I=I=Fap(AYV)9G)w(+Ec+dyG;m6esV!+S5Utu!b6cJZFU zhD~~1dcYvkO!YY8u;IgwfHs7kW17NEzBHtjo$I7Rc9%+cp0A_tk*kU1+}CXRkd-hT z4Er?{v;1I7K)tjKjMbRB{ONtyxUpZ=DfX-1Z=J|nHL+MoayKY0S1W#gWiaArruf#| z*JHuexpRN9Y?>AT<*_3(?$ z1GXBelvnJzDVd41O!nppQtnzc22LMNPI?Y-j73Ns%ZFG|yIAIP+!k?qthBVW=aCGP zL&46@_njP6dY>1+@Pw8oKJnCu`Vp5jZ-9tpdmY(ZlO~HVa~>5xe7@?zX>P6Qbg6JxdN7qvm%D;l zzZm_YL-_myrs@3$qBk5O>MNMEZowsxHmiX$m2qDK_k6~2wk| zUM$UPcNcY43E4E}>v~70N!KKIZ2W+)9ALvP&rpa9cBPg(%DJ4e&u&yiO-Q6CQj=xQ zbDF~!C+*e>nxV#EA2QoSFa5))#K*RH-gs_BIgP;?74^{S9hX}YU-1YevRhzR;^JTgSrW50VKjEhR(P(6NSL(D`jT zo2-LJ-eKyS_9U7WId2>X_p0b7USU_wPdC#0XCD z(*?Sr+#c?C2LAT;_VO;u+7852$bEW=9(H{p8}`gx2tnPgjE%2ytBsC*mM?bjGyH9G za?)AA0wVpt+nl2W?sdfodKS`yX2!>ZKYuTLfC_xztKxyXRYXDTLT@cZu$=-sf7eKJ z-+RRjbKfE`Mtou|QE*MOd$9c6Iq9+k8)DF7YHCU^P>>K`t#4Qklh4tK`IcqyVq;%% z{x=fnTgQxo$0{_t%XGe`CwJf3IDIh`p3}Kv4bxmuz0V+P(51+M+K}?mqqp9C#Srp2 zETfdrVQP*~yWDE&?6AIc*}(pqGTh|T$PHbU75-ynd6r>}AGjcVWdsmWU#^qob+xKJ zaE}Ny@vMgT?`P|F3kq{de+QkdWBhDxF2|Ij4{G6BriBnkMB@TGBk)pVxiAPXl|0OSi)D;tHoa3d!8I^PCAs8e?vVsf%8!O!hbCd8l{1 zH@_`h4Hp`b-PcwW8L>YBI^_(8b^me;a=0fijok1VG!4Ccxx>#Ag4#TzAW_Vj{LB@m zlD`t~?VE?U69yjq(HoUN+y_?9e=KSqw>yg3-x|2F#(2oXGu+>p($1wAv%|$zF?)gH zaLDeyWB+c(Y({;|mp9w^M1&>OsFQ3?C^e;oI5Js&bqwQCdd*2xSCkoR;4x#VW>aFU zyNbFA>ud$ib~HIHFj1KaU9Tcn19U8Pl#!*4LYm# z2ITo_zP;ow5Z|>wvOG5HqPihf8X)@!y3Vdrglo$0n5SoqqoeiNhdfz#+WguOkc!Ew z{;MH5PtI^Bn$Z5PAS?|*v`uZlWtQ)SHI%$s0O)qGpKo*M6z6&rL!7?>^{vGkqr%^yGTefV0 zA{w7i=p`ivm0Kxk>E+M80~MMKNw%FK`g#TS1MT6D~fe)larBxhtKw_Xqo>XFfDL!z(H zTc2I%Z8w+POS=xEr?ASzT2EHXiDymIRZA;3H_?7mkHFpRM4r{(C66ZIZMs%}R5?O*t!ZT!L zVm$|$^6~_I+O;PwXMQr3O6_W}A&i+AtoWBly`LlaBUwJs_|c3q6Pf^cxrYhg|H)bW z_aoo&FQYKtRpFdP<5ofU%=VCY5s{vwA|k>V^WW>L(1gI{E`*8yUcy`cueZIn5!)s5 XC7?3rOU}7+;d5h7+nAM^y2t$o$l(nb literal 0 HcmV?d00001 diff --git a/packages/geo-diary/src/components/common/appointment-detail/__tests__/__snapshots__/appointment-detail.tsx.snap b/packages/geo-diary/src/components/common/appointment-detail/__tests__/__snapshots__/appointment-detail.tsx.snap index 8200bb7d60..36c31fa894 100644 --- a/packages/geo-diary/src/components/common/appointment-detail/__tests__/__snapshots__/appointment-detail.tsx.snap +++ b/packages/geo-diary/src/components/common/appointment-detail/__tests__/__snapshots__/appointment-detail.tsx.snap @@ -60,7 +60,7 @@ exports[`AppointmentModal AppointmentModal should render correctly 1`] = ` } >

- 42, Mill Road, Sharnbrook, Bedford, Bedfordshire, MK44 1NX, GB + 42, Mill Road, Sharnbrook, Bedford, Bedfordshire, MK44 1NX

@@ -156,11 +156,41 @@ exports[`AppointmentModal renderCheckMark should match snapshot 1`] = ` `; -exports[`AppointmentModal renderCommunicationDetail should match snapshot 1`] = ` +exports[`AppointmentModal renderCommunicationDetail should match snapshot for commuication details 1`] = `
, + "text": + 070000000 + , + }, + Object { + "icon": , + "text": + 070000000 + , + }, + Object { + "icon": , + "text": + 070000000 + , + }, Object { "icon": `; -exports[`AppointmentModal renderCommunicationType should match snapshot 1`] = ` +exports[`AppointmentModal renderCommunicationDetail should match snapshot for empty commuication details 1`] = `
-
`; diff --git a/packages/geo-diary/src/components/common/appointment-detail/__tests__/appointment-detail.tsx b/packages/geo-diary/src/components/common/appointment-detail/__tests__/appointment-detail.tsx index cb19f2aba0..92fe31ca51 100644 --- a/packages/geo-diary/src/components/common/appointment-detail/__tests__/appointment-detail.tsx +++ b/packages/geo-diary/src/components/common/appointment-detail/__tests__/appointment-detail.tsx @@ -6,10 +6,8 @@ import { renderAddress, renderCheckMark, renderCommunicationDetail, - renderCommunicationType, mapStateToProps, mapDispatchToProps, - renderHrefLink, filterLoggedInUser, getAdditionalAttendees, getApplicantAttendees, @@ -292,71 +290,26 @@ describe('AppointmentModal', () => { }) describe('renderCommunicationDetail', () => { - it('should match snapshot', () => { - const input = [ - { - label: 'E-Mail', - detail: 'chase.maclean@reapitestates.net', - }, - ] + it('should match snapshot for commuication details', () => { + const input = { + homePhone: '070000000', + workPhone: '070000000', + mobilePhone: '070000000', + email: 'chase.maclean@reapitestates.net', + } const data = renderCommunicationDetail(input) const wrapper = shallow(
{data}
) expect(wrapper).toMatchSnapshot() }) - it('should show when have comminicationDetail', () => { - const input = [ - { - label: 'E-Mail', - detail: 'chase.maclean@reapitestates.net', - }, - ] - const data = renderCommunicationDetail(input) - expect(data).not.toBeNull() - }) - it('should return null when doesnt have', () => { - const input = undefined - const data = renderCommunicationDetail(input) - expect(data).toBeNull() - }) - }) - describe('renderCommunicationType', () => { - it('should match snapshot', () => { - const input = 'E-Mail' - const data = renderCommunicationType(input) + it('should match snapshot for empty commuication details', () => { + const input = {} + const data = renderCommunicationDetail(input) const wrapper = shallow(
{data}
) expect(wrapper).toMatchSnapshot() }) - it('should return ', () => { - const input = 'E-Mail' - const data = renderCommunicationType(input) - const wrapper = shallow(
{data}
) - expect(wrapper.find('TiMail')).toHaveLength(1) - }) - it('should return ', () => { - const input = 'Home' - const data = renderCommunicationType(input) - const wrapper = shallow(
{data}
) - expect(wrapper.find('TiHome')).toHaveLength(1) - }) - it('should return ', () => { - const input = 'Mobile' - const data = renderCommunicationType(input) - const wrapper = shallow(
{data}
) - expect(wrapper.find('TiDevicePhone')).toHaveLength(1) - }) - it('should return ', () => { - const input = 'Work' - const data = renderCommunicationType(input) - const wrapper = shallow(
{data}
) - expect(wrapper.find('TiPhoneOutline')).toHaveLength(1) - }) - it('should return null', () => { - const input = '' - const data = renderCommunicationType(input) - expect(data).toBeNull() - }) }) + describe('mapStateToProps', () => { it('should run correctly', () => { // @ts-ignore: only pick neccessary props @@ -389,32 +342,18 @@ describe('AppointmentModal', () => { appointmentTypes: [], loginMode: 'DESKTOP', attendee: { + id: 'BED160186', + type: 'applicant', contacts: [ { - communicationDetails: [ - { - detail: '01632 963403', - label: 'Home', - }, - { - detail: '07700 903403', - label: 'Mobile', - }, - { - detail: '020 7946 3403', - label: 'Work', - }, - { - detail: 'kgeddes225@rpsfiction.net', - label: 'E-Mail', - }, - ], id: 'BED16000217', name: 'Ms Kali Geddes', + homePhone: '01632 963403', + mobilePhone: '07700 903403', + workPhone: '020 7946 3403', + email: 'kgeddes225@rpsfiction.net', }, ], - id: 'BED160186', - type: 'applicant', }, negotiators: [ { @@ -478,20 +417,7 @@ describe('AppointmentModal', () => { expect(mockDispatch).toBeCalled() }) }) - describe('renderHrefLink', () => { - it('should run correctly and return mailto', () => { - const input = 'E-Mail' - const output = 'mailto:' - const result = renderHrefLink(input) - expect(result).toEqual(output) - }) - it('should run correctly and return tel:', () => { - const input = 'Home' - const output = 'tel:' - const result = renderHrefLink(input) - expect(result).toEqual(output) - }) - }) + describe('filterLoggedInUser', () => { it('should run and filter correctly 1', () => { const input = [ diff --git a/packages/geo-diary/src/components/common/appointment-detail/appointment-detail.tsx b/packages/geo-diary/src/components/common/appointment-detail/appointment-detail.tsx index 3b73c0f829..072e2bd66f 100644 --- a/packages/geo-diary/src/components/common/appointment-detail/appointment-detail.tsx +++ b/packages/geo-diary/src/components/common/appointment-detail/appointment-detail.tsx @@ -20,7 +20,6 @@ import { FlexContainerResponsive, } from '@reapit/elements' import { - AppointmentAttendeeCommunicationModel, ListItemModel, AppointmentAttendeeModel, NegotiatorModel, @@ -30,10 +29,11 @@ import { import { ReduxState, ExtendedAppointmentModel } from '@/types/core' import { appointmentDetailHideModal, showHideConfirmModal } from '@/actions/appointment-detail' import styles from '@/styles/ui/appoinments-detail.scss?mod' -import { capitalizeFirstLetter } from '@/utils/capitalizeFirstLetter' +import { capitalizeFirstLetter } from '@/utils/capitalize-first-letter' import { getAttendeeEntityType } from '@/utils/get-attendee-entity-type' import ConfirmContent from './confirm-content' import { LoginMode } from '@reapit/cognito-auth' +import { IconListItem } from '../../../../../elements/src/components/IconList/index' const { appointmentDetailTextContainer } = styles @@ -50,50 +50,39 @@ interface GetHeaderParams { type?: ListItemModel | null } -export const renderCommunicationType = (communicationLabel: string | undefined) => { - switch (communicationLabel) { - case 'E-Mail': - return - case 'Home': - return - case 'Mobile': - return - case 'Work': - return - default: - return null +export const renderCommunicationDetail = (communicationDetails: AppointmentContactModel) => { + const { homePhone, workPhone, mobilePhone, email } = communicationDetails + const items: IconListItem[] = [] + + if (homePhone) { + items.push({ + icon: , + text: {homePhone}, + }) } -} -export const renderHrefLink = (communicationLabel: string | undefined) => { - switch (communicationLabel) { - case 'E-Mail': - return 'mailto:' - default: - return 'tel:' + if (workPhone) { + items.push({ + icon: , + text: {workPhone}, + }) } -} -export const renderCommunicationDetail = ( - communicationDetails: AppointmentAttendeeCommunicationModel[] | undefined, -) => { - if (!communicationDetails) { - return null + if (mobilePhone) { + items.push({ + icon: , + text: {mobilePhone}, + }) } - return ( - { - return { - icon: renderCommunicationType(communicationDetail.label), - text: ( - - {communicationDetail.detail} - - ), - } - })} - /> - ) + + if (email) { + items.push({ + icon: , + text: {email}, + }) + } + + return } export const renderCheckMark = (isConfirmed: boolean | undefined) => { @@ -235,7 +224,7 @@ export const renderAttendee = (attendee: AppointmentAttendeeModel, loginMode: Lo

{contact?.name}

- {renderCommunicationDetail(contact?.communicationDetails || [])} + {renderCommunicationDetail(contact)}

) diff --git a/packages/geo-diary/src/components/container/__tests__/__snapshots__/map.tsx.snap b/packages/geo-diary/src/components/container/__tests__/__snapshots__/map.tsx.snap index 592652720a..7d1265861a 100644 --- a/packages/geo-diary/src/components/container/__tests__/__snapshots__/map.tsx.snap +++ b/packages/geo-diary/src/components/container/__tests__/__snapshots__/map.tsx.snap @@ -13,7 +13,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -34,7 +33,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "Cornerways", "buildingNumber": "68", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -55,7 +53,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "The Pines", "buildingNumber": "34", - "country": "GB", "geolocation": Object { "latitude": 51.924759, "longitude": -0.929362, @@ -76,7 +73,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -97,7 +93,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -118,7 +113,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "The Cottage", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.29491, "longitude": -0.512901, @@ -139,7 +133,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "49", - "country": "GB", "geolocation": Object { "latitude": 52.188155, "longitude": -0.604618, @@ -160,7 +153,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "39", - "country": "GB", "geolocation": Object { "latitude": 52.044516, "longitude": -0.689953, @@ -181,7 +173,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "The Firs", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.105969, "longitude": -0.353784, @@ -202,7 +193,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -223,7 +213,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "The Homestead", "buildingNumber": "57", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -244,7 +233,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "54", - "country": "GB", "geolocation": Object { "latitude": 52.161384, "longitude": -0.461113, @@ -265,7 +253,6 @@ exports[`Map Should match snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "8", - "country": "", "geolocation": Object { "latitude": 52.04681, "longitude": -0.710721, diff --git a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/home.tsx.snap b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/home.tsx.snap index 893768acbf..2e5c195c17 100644 --- a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/home.tsx.snap +++ b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/home.tsx.snap @@ -69,7 +69,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -108,7 +107,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "Cornerways", "buildingNumber": "68", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -147,7 +145,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "The Pines", "buildingNumber": "34", - "country": "GB", "geolocation": Object { "latitude": 51.924759, "longitude": -0.929362, @@ -186,7 +183,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -271,7 +267,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -356,7 +351,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "The Cottage", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.29491, "longitude": -0.512901, @@ -418,7 +412,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "49", - "country": "GB", "geolocation": Object { "latitude": 52.188155, "longitude": -0.604618, @@ -457,7 +450,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "39", - "country": "GB", "geolocation": Object { "latitude": 52.044516, "longitude": -0.689953, @@ -496,7 +488,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "The Firs", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.105969, "longitude": -0.353784, @@ -535,7 +526,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -574,7 +564,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "The Homestead", "buildingNumber": "57", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -613,7 +602,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "54", - "country": "GB", "geolocation": Object { "latitude": 52.161384, "longitude": -0.461113, @@ -652,7 +640,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "8", - "country": "", "geolocation": Object { "latitude": 52.04681, "longitude": -0.710721, @@ -718,7 +705,6 @@ exports[`Home should match a snapshot 1`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, diff --git a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap index daa510a6f4..418f9bf5cb 100644 --- a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap +++ b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap @@ -1,106 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Login renderForm should match snapshot 1`] = ` +exports[`Login should match a snapshot 1`] = `
-
- - - - Login - + Reapit Connect Graphic - -
-`; - -exports[`Login renderForm should match snapshot 2`] = ` -
-
- - +

+ Welcome to Geo Diary +

Login - - -
-`; - -exports[`Login should match a snapshot 1`] = ` -
-
- - Sign in - -

- Welcome to GEO Diary -

- - -
({ + redirectToLogin: jest.fn(), +})) describe('Login', () => { it('should match a snapshot', () => { - let mock: any = jest.fn() - const props: LoginProps = { - error: false, - login: mock, - location: mock, - history: mock, - match: mock, hasSession: false, } @@ -31,74 +19,26 @@ describe('Login', () => { }) it('should match a snapshot', () => { - let mock: any = jest.fn() - const props: LoginProps = { - error: false, - login: mock, - location: mock, - history: mock, - match: mock, hasSession: true, } expect(shallow()).toMatchSnapshot() }) - describe('onSubmitHandler', () => { - it('should run correctly', () => { - const mockSetIsSubmitting = jest.fn() - const mockLogin = jest.fn() - const fn = onSubmitHandler(mockSetIsSubmitting, mockLogin) - const mockValues = { - email: '', - password: '', + describe('loginHandler', () => { + it('should correctly call redirect on click', () => { + const props: LoginProps = { + hasSession: false, } - fn(mockValues) - expect(mockSetIsSubmitting).toHaveBeenCalledWith(true) - expect(mockLogin).toHaveBeenCalledWith({ - userName: mockValues.email, - password: mockValues.password, - loginType: LOGIN_TYPE.CLIENT, - }) - }) - - it('should run correctly', () => { - const mockSetIsSubmitting = jest.fn() - const mockLogin = jest.fn() - const fn = onSubmitHandler(mockSetIsSubmitting, mockLogin) - const mockValues = {} as LoginFormValues - fn(mockValues) - expect(mockSetIsSubmitting).toHaveBeenCalledWith(true) - expect(mockLogin).toHaveBeenCalledWith({ - userName: mockValues.email, - password: mockValues.password, - loginType: LOGIN_TYPE.CLIENT, - }) - }) - }) - - describe('renderForm', () => { - it('should match snapshot', () => { - const component = renderForm({ isSubmitting: true, error: undefined }) - const wrapper = shallow(
{component()}
) - expect(wrapper).toMatchSnapshot() - }) + const wrapper = shallow() - it('should match snapshot', () => { - const component = renderForm({ isSubmitting: false, error: new Error('mock Error') }) - const wrapper = shallow(
{component()}
) - expect(wrapper).toMatchSnapshot() - }) - }) + wrapper + .find(Button) + .first() + .simulate('click') - describe('handleUseEffect', () => { - it('should run correctly', () => { - const mockSetIsSubmitting = jest.fn() - const mockError = new Error('mock Error') - const fn = handleUseEffect({ setIsSubmitting: mockSetIsSubmitting, error: mockError }) - fn() - expect(mockSetIsSubmitting).toBeCalledWith(false) + expect(redirectToLogin).toHaveBeenCalledTimes(1) }) }) @@ -107,39 +47,13 @@ describe('Login', () => { const mockState = { auth: { loginSession: {}, - error: false, }, } as ReduxState const expected = { hasSession: true, - error: false, } const result = mapStateToProps(mockState) expect(result).toEqual(expected) }) - - it('should run correctly', () => { - const mockState = { - auth: { - error: true, - }, - } as ReduxState - const expected = { - hasSession: false, - error: true, - } - const result = mapStateToProps(mockState) - expect(result).toEqual(expected) - }) - }) - - describe('mapDispatchToProps', () => { - it('should run correctly', () => { - const mockDispatch = jest.fn() - const { login } = mapDispatchToProps(mockDispatch) - const mockParams = { userName: '', password: '', loginType: LOGIN_TYPE.CLIENT } as LoginParams - login(mockParams) - expect(mockDispatch).toBeCalled() - }) }) }) diff --git a/packages/geo-diary/src/components/pages/login.tsx b/packages/geo-diary/src/components/pages/login.tsx index 942225a0b1..f16178aa85 100644 --- a/packages/geo-diary/src/components/pages/login.tsx +++ b/packages/geo-diary/src/components/pages/login.tsx @@ -1,86 +1,28 @@ import * as React from 'react' import { connect } from 'react-redux' import { Redirect } from 'react-router-dom' -import { Dispatch } from 'redux' -import { withRouter, RouteComponentProps } from 'react-router' import { ReduxState } from '@/types/core' -import { authLogin } from '@/actions/auth' -import { validate } from '@/utils/form/login' import Routes from '@/constants/routes' -import { LOGIN_TYPE } from '@/constants/auth' -import { Input, Button, H1, Level, Alert, Formik, Form } from '@reapit/elements' -import { LoginParams } from '@reapit/cognito-auth' +import { Level, Button } from '@reapit/elements' +import { LoginParams, redirectToLogin } from '@reapit/cognito-auth' import loginStyles from '@/styles/pages/login.scss?mod' import logoImage from '@/assets/images/reapit-graphic.jpg' +import connectImage from '@/assets/images/reapit-connect.png' export interface LoginMappedActions { login: (params: LoginParams) => void } -export interface LoginMappedProps { +export interface LoginProps { hasSession: boolean - error: boolean } -export interface LoginFormValues { - email: string - password: string -} - -export type LoginProps = LoginMappedActions & LoginMappedProps & RouteComponentProps - -export const onSubmitHandler = (setIsSubmitting: any, login: any) => (values: LoginFormValues) => { - const { email, password } = values - setIsSubmitting(true) - login({ - userName: email, - password, - loginType: LOGIN_TYPE.CLIENT, - cognitoClientId: process.env.COGNITO_CLIENT_ID_GEO_DIARY as string, - } as LoginParams) -} - -export const renderForm = ({ isSubmitting, error }) => () => ( -
- - - - - - - - {error && } - -) - -export const handleUseEffect = ({ setIsSubmitting, error }) => () => { - if (error) { - setIsSubmitting(false) - } -} +const loginHandler = () => + redirectToLogin(process.env.COGNITO_CLIENT_ID_GEO_DIARY as string, `${window.location.origin}`) export const Login: React.FunctionComponent = (props: LoginProps) => { - const [isSubmitting, setIsSubmitting] = React.useState(false) - const { hasSession, error, login } = props - const { disabled, wrapper, container, image } = loginStyles - - React.useEffect(handleUseEffect({ setIsSubmitting, error }), [error]) + const { hasSession } = props + const { wrapper, container, image } = loginStyles if (hasSession) { return @@ -88,17 +30,16 @@ export const Login: React.FunctionComponent = (props: LoginProps) => return (
-
-

Sign in

-

Welcome to GEO Diary

- - - {renderForm({ isSubmitting, error })} - +
+ + Reapit Connect Graphic + +

Welcome to Geo Diary

+ + +
@@ -108,13 +49,8 @@ export const Login: React.FunctionComponent = (props: LoginProps) => ) } -export const mapStateToProps = (state: ReduxState): LoginMappedProps => ({ +export const mapStateToProps = (state: ReduxState): LoginProps => ({ hasSession: !!state.auth.loginSession || !!state.auth.refreshSession, - error: state.auth.error, -}) - -export const mapDispatchToProps = (dispatch: Dispatch): LoginMappedActions => ({ - login: (params: LoginParams) => dispatch(authLogin(params)), }) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login)) +export default connect(mapStateToProps, {})(Login) diff --git a/packages/geo-diary/src/components/ui/__tests__/__snapshots__/appointment-list.tsx.snap b/packages/geo-diary/src/components/ui/__tests__/__snapshots__/appointment-list.tsx.snap index e3e25cf64f..a8723ca875 100644 --- a/packages/geo-diary/src/components/ui/__tests__/__snapshots__/appointment-list.tsx.snap +++ b/packages/geo-diary/src/components/ui/__tests__/__snapshots__/appointment-list.tsx.snap @@ -49,7 +49,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -137,7 +136,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "Cornerways", "buildingNumber": "68", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -225,7 +223,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "The Pines", "buildingNumber": "34", - "country": "GB", "geolocation": Object { "latitude": 51.924759, "longitude": -0.929362, @@ -313,7 +310,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -479,7 +475,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "56", - "country": "GB", "geolocation": Object { "latitude": 52.079532, "longitude": -0.790871, @@ -645,7 +640,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "The Cottage", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.29491, "longitude": -0.512901, @@ -772,7 +766,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "49", - "country": "GB", "geolocation": Object { "latitude": 52.188155, "longitude": -0.604618, @@ -860,7 +853,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "39", - "country": "GB", "geolocation": Object { "latitude": 52.044516, "longitude": -0.689953, @@ -948,7 +940,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "The Firs", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 52.105969, "longitude": -0.353784, @@ -1036,7 +1027,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "Wayside", "buildingNumber": "", - "country": "GB", "geolocation": Object { "latitude": 51.989592, "longitude": -1.06774, @@ -1124,7 +1114,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "The Homestead", "buildingNumber": "57", - "country": "GB", "geolocation": Object { "latitude": 52.135071, "longitude": -0.45936, @@ -1212,7 +1201,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "54", - "country": "GB", "geolocation": Object { "latitude": 52.161384, "longitude": -0.461113, @@ -1300,7 +1288,6 @@ exports[`AppointmentList Should match snapshot if having no data 2`] = ` "address": Object { "buildingName": "", "buildingNumber": "8", - "country": "", "geolocation": Object { "latitude": 52.04681, "longitude": -0.710721, diff --git a/packages/geo-diary/src/components/ui/appointment-list.tsx b/packages/geo-diary/src/components/ui/appointment-list.tsx index 03f54aa001..0548754855 100644 --- a/packages/geo-diary/src/components/ui/appointment-list.tsx +++ b/packages/geo-diary/src/components/ui/appointment-list.tsx @@ -92,10 +92,7 @@ export const AppointmentListComponent = ({ let renderETAButton: React.ReactNode = null if (displayETAButton) { - const tel = (nextAppointment?.attendeeWithMobile?.communicationDetails || []).filter( - ({ label }) => label === 'Mobile', - )[0].detail - + const tel = nextAppointment?.attendeeWithMobile?.mobilePhone || '' const name = nextAppointment?.attendeeWithMobile?.name || '' const negName = nextAppointment?.currentNegotiator?.name || '' const duration = nextAppointment?.durationText diff --git a/packages/geo-diary/src/constants/api.ts b/packages/geo-diary/src/constants/api.ts index 5bf7c31587..6391424ed7 100644 --- a/packages/geo-diary/src/constants/api.ts +++ b/packages/geo-diary/src/constants/api.ts @@ -1,10 +1,13 @@ import { StringMap } from '@/types/core' +import { COOKIE_SESSION_KEY } from '@reapit/cognito-auth' export const APPOINTMENTS_HEADERS = { 'Content-Type': 'application/json', } as StringMap -export const API_VERSION = '2020-01-31' +export const API_VERSION = '2020-02-06' + +export const COOKIE_SESSION_KEY_GEO_DIARY = `${COOKIE_SESSION_KEY}-geo-diary` export const URLS = { appointments: '/appointments', diff --git a/packages/geo-diary/src/core/store.ts b/packages/geo-diary/src/core/store.ts index fe338dadbb..458038c708 100644 --- a/packages/geo-diary/src/core/store.ts +++ b/packages/geo-diary/src/core/store.ts @@ -98,6 +98,10 @@ export class Store { window.addEventListener('offline', () => this.dispatch({ type: ActionTypes.OFFLINE })) } + async purgeStore() { + Store.instance.persistor.purge() + } + hotModuleReloading() { const hotModule = (module as any).hot diff --git a/packages/geo-diary/src/reducers/auth.ts b/packages/geo-diary/src/reducers/auth.ts index b0b081cdd9..f8593b5002 100644 --- a/packages/geo-diary/src/reducers/auth.ts +++ b/packages/geo-diary/src/reducers/auth.ts @@ -1,7 +1,8 @@ import { Action } from '@/types/core' import { isType } from '@/utils/actions' import { authLogin, authLoginFailure, authLoginSuccess, authLogoutSuccess, authSetRefreshSession } from '@/actions/auth' -import { RefreshParams, LoginSession } from '@reapit/cognito-auth' +import { RefreshParams, LoginSession, getSessionCookie } from '@reapit/cognito-auth' +import { COOKIE_SESSION_KEY_GEO_DIARY } from '../constants/api' export interface AuthState { error: boolean @@ -9,10 +10,11 @@ export interface AuthState { refreshSession: RefreshParams | null } export const defaultState = (): AuthState => { + const refreshSession = getSessionCookie(COOKIE_SESSION_KEY_GEO_DIARY) return { error: false, loginSession: null, - refreshSession: null, + refreshSession, } } @@ -40,7 +42,10 @@ const authReducer = (state: AuthState = defaultState(), action: Action): Au } if (isType(action, authLogoutSuccess)) { - return defaultState() + return { + ...defaultState(), + refreshSession: null, + } } if (isType(action, authSetRefreshSession)) { diff --git a/packages/geo-diary/src/sagas/__stubs__/appointment.ts b/packages/geo-diary/src/sagas/__stubs__/appointment.ts index b9b168dbe3..21320f7e7b 100644 --- a/packages/geo-diary/src/sagas/__stubs__/appointment.ts +++ b/packages/geo-diary/src/sagas/__stubs__/appointment.ts @@ -30,7 +30,6 @@ export const appointmentDataStub: ExtendedAppointmentModel = { line3: 'Bedford', line4: 'Bedfordshire', postcode: 'MK44 1NX', - country: 'GB', geolocation: { latitude: 52.223253, longitude: -0.532454, @@ -57,24 +56,10 @@ export const appointmentDataStub: ExtendedAppointmentModel = { { id: 'BED16000217', name: 'Ms Kali Geddes', - communicationDetails: [ - { - label: 'Home', - detail: '01632 963403', - }, - { - label: 'Mobile', - detail: '07700 903403', - }, - { - label: 'Work', - detail: '020 7946 3403', - }, - { - label: 'E-Mail', - detail: 'kgeddes225@rpsfiction.net', - }, - ], + homePhone: '01632 963403', + mobilePhone: '07700 903403', + workPhone: '020 7946 3403', + email: 'kgeddes225@rpsfiction.net', }, ], }, diff --git a/packages/geo-diary/src/sagas/__stubs__/appointments.ts b/packages/geo-diary/src/sagas/__stubs__/appointments.ts index 867a43e3f2..178df7f527 100644 --- a/packages/geo-diary/src/sagas/__stubs__/appointments.ts +++ b/packages/geo-diary/src/sagas/__stubs__/appointments.ts @@ -21,7 +21,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Old Haversham', line4: 'Milton Keynes', postcode: 'MK19 7DZ', - country: 'GB', geolocation: { latitude: 52.079532, longitude: -0.790871, @@ -60,7 +59,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedfordshire', line4: '', postcode: 'MK40 3PD', - country: 'GB', geolocation: { latitude: 52.135071, longitude: -0.45936, @@ -99,7 +97,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Buckingham', line4: 'Buckinghamshire', postcode: 'MK18 2LZ', - country: 'GB', geolocation: { latitude: 51.924759, longitude: -0.929362, @@ -138,7 +135,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Buckinghamshire', line4: '', postcode: 'MK18 4AG', - country: 'GB', geolocation: { latitude: 51.989592, longitude: -1.06774, @@ -223,7 +219,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Old Haversham', line4: 'Milton Keynes', postcode: 'MK19 7DZ', - country: 'GB', geolocation: { latitude: 52.079532, longitude: -0.790871, @@ -308,7 +303,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedfordshire', line4: '', postcode: 'MK44 1AQ', - country: 'GB', geolocation: { latitude: 52.29491, longitude: -0.512901, @@ -370,7 +364,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedford', line4: 'Bedfordshire', postcode: 'MK43 7LU', - country: 'GB', geolocation: { latitude: 52.188155, longitude: -0.604618, @@ -409,7 +402,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Milton Keynes', line4: 'Buckinghamshire', postcode: 'MK10 7BP', - country: 'GB', geolocation: { latitude: 52.044516, longitude: -0.689953, @@ -448,7 +440,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedfordshire', line4: '', postcode: 'SG18 9AW', - country: 'GB', geolocation: { latitude: 52.105969, longitude: -0.353784, @@ -487,7 +478,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Buckinghamshire', line4: '', postcode: 'MK18 4AG', - country: 'GB', geolocation: { latitude: 51.989592, longitude: -1.06774, @@ -526,7 +516,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedfordshire', line4: '', postcode: 'MK40 3PD', - country: 'GB', geolocation: { latitude: 52.135071, longitude: -0.45936, @@ -565,7 +554,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Bedfordshire', line4: '', postcode: 'MK41 7UJ', - country: 'GB', geolocation: { latitude: 52.161384, longitude: -0.461113, @@ -604,7 +592,6 @@ export const appointmentsDataStub: AppointmentsData = { line3: 'Milton Keynes', line4: '', postcode: 'MK10 9FS', - country: '', geolocation: { latitude: 52.04681, longitude: -0.710721, diff --git a/packages/geo-diary/src/sagas/__stubs__/offices.ts b/packages/geo-diary/src/sagas/__stubs__/offices.ts index a1574d0cec..39cd90a984 100644 --- a/packages/geo-diary/src/sagas/__stubs__/offices.ts +++ b/packages/geo-diary/src/sagas/__stubs__/offices.ts @@ -7,7 +7,6 @@ export const officeStub: OfficeModel = { name: 'string', manager: 'string', address: { - type: 'string', buildingName: 'string', buildingNumber: 'string', line1: 'string', diff --git a/packages/geo-diary/src/sagas/__stubs__/properties.ts b/packages/geo-diary/src/sagas/__stubs__/properties.ts index 205e884224..4ea5c7e489 100644 --- a/packages/geo-diary/src/sagas/__stubs__/properties.ts +++ b/packages/geo-diary/src/sagas/__stubs__/properties.ts @@ -14,7 +14,6 @@ export const propertyStub: PropertyModel = { line3: 'string', line4: 'string', postcode: 'string', - country: 'string', geolocation: { latitude: 0, longitude: 0, diff --git a/packages/geo-diary/src/sagas/__tests__/appointments.ts b/packages/geo-diary/src/sagas/__tests__/appointments.ts index 7dfdce6cfb..219f2ebc70 100644 --- a/packages/geo-diary/src/sagas/__tests__/appointments.ts +++ b/packages/geo-diary/src/sagas/__tests__/appointments.ts @@ -4,7 +4,7 @@ import appointmentsSagas, { getStartAndEndDate, } from '@/sagas/appointments' import { ListItemModel } from '@reapit/foundations-ts-definitions' -import { sortAppoinmentsByStartTime } from '@/utils/sortAppoinmentsByStartTime' +import { sortAppoinmentsByStartTime } from '@/utils/sort-appointments-by-start-time' import ActionTypes from '@/constants/action-types' import { put, takeLatest, all, fork, call, select } from '@redux-saga/core/effects' import { diff --git a/packages/geo-diary/src/sagas/__tests__/auth.ts b/packages/geo-diary/src/sagas/__tests__/auth.ts index 3debf3dd0b..9e63026a2a 100644 --- a/packages/geo-diary/src/sagas/__tests__/auth.ts +++ b/packages/geo-diary/src/sagas/__tests__/auth.ts @@ -1,24 +1,22 @@ import authSagas, { doLogin, doLogout, loginListen, logoutListen } from '../auth' import ActionTypes from '../../constants/action-types' import { put, all, takeLatest, call } from '@redux-saga/core/effects' -import { authLogoutSuccess, authLoginSuccess, authLoginFailure } from '../../actions/auth' -import { history } from '../../core/router' -import Routes from '../../constants/routes' +import { authLoginSuccess, authLoginFailure } from '../../actions/auth' import { Action, ActionType } from '@/types/core' import { mockLoginSession } from '@/utils/__mocks__/session' import errorMessages from '@/constants/error-messages' -import { LoginParams, setUserSession, removeSession } from '@reapit/cognito-auth' +import { LoginParams, setUserSession, removeSession, redirectToLogout } from '@reapit/cognito-auth' +import store from '@/core/store' jest.mock('../../utils/session') -jest.mock('../../core/store', () => ({ - persistor: { - purge: jest.fn(), - }, +jest.mock('@/core/store', () => ({ + purgeStore: jest.fn(), })) -jest.mock('../../core/router', () => ({ - history: { - push: jest.fn(), - }, + +jest.mock('@reapit/cognito-auth', () => ({ + setUserSession: jest.fn(), + removeSession: jest.fn(), + redirectToLogout: jest.fn(), })) describe('auth sagas', () => { @@ -53,17 +51,17 @@ describe('auth sagas', () => { describe('authLogout', () => { it('should redirect to login page', () => { const gen = doLogout() - expect(gen.next().value).toEqual(call(removeSession)) + + expect(gen.next()).toEqual(call(store.purgeStore)) + + expect(gen.next()).toEqual(call(removeSession)) gen.next() - expect(history.push).toHaveBeenCalledTimes(1) - expect(history.push).toHaveBeenLastCalledWith(Routes.LOGIN) - expect(gen.next().value).toEqual(put(authLogoutSuccess())) + expect(redirectToLogout).toHaveBeenCalledTimes(1) expect(gen.next().done).toBe(true) }) it('on logout fail', () => { const gen = doLogout() - expect(gen.next().value).toEqual(call(removeSession)) gen.next(gen.throw(new Error(errorMessages.DEFAULT_SERVER_ERROR)).value) expect(gen.next().done).toBe(true) }) diff --git a/packages/geo-diary/src/sagas/appointments.ts b/packages/geo-diary/src/sagas/appointments.ts index ba2a0cf75c..cef4fa11e3 100644 --- a/packages/geo-diary/src/sagas/appointments.ts +++ b/packages/geo-diary/src/sagas/appointments.ts @@ -23,7 +23,7 @@ import { selectOnlineStatus } from '@/selectors/online' import { selectUserCode } from '@/selectors/auth' import { AppointmentRequestParams } from '@/actions/appointments' import { initAuthorizedRequestHeaders } from '@/utils/api' -import { sortAppoinmentsByStartTime } from '@/utils/sortAppoinmentsByStartTime' +import { sortAppoinmentsByStartTime } from '@/utils/sort-appointments-by-start-time' import utc from 'dayjs/plugin/utc' import { fetchAppointmentMetadata } from './api' diff --git a/packages/geo-diary/src/sagas/auth.ts b/packages/geo-diary/src/sagas/auth.ts index b951a1e179..bc735e3cfb 100644 --- a/packages/geo-diary/src/sagas/auth.ts +++ b/packages/geo-diary/src/sagas/auth.ts @@ -1,10 +1,10 @@ import { takeLatest, put, call, all } from '@redux-saga/core/effects' -import { history } from '@/core/router' import { Action } from '@/types/core.ts' -import Routes from '@/constants/routes' import ActionTypes from '@/constants/action-types' -import { authLoginSuccess, authLoginFailure, authLogoutSuccess } from '@/actions/auth' -import { LoginParams, LoginSession, setUserSession, removeSession } from '@reapit/cognito-auth' +import { authLoginSuccess, authLoginFailure } from '@/actions/auth' +import { LoginParams, LoginSession, setUserSession, removeSession, redirectToLogout } from '@reapit/cognito-auth' +import { COOKIE_SESSION_KEY_GEO_DIARY } from '../constants/api' +import store from '@/core/store' export const doLogin = function*({ data }: Action) { try { @@ -23,9 +23,9 @@ export const doLogin = function*({ data }: Action) { export const doLogout = function*() { try { - yield call(removeSession) - yield history.push(Routes.LOGIN) - yield put(authLogoutSuccess()) + yield call(store.purgeStore) + yield call(removeSession, COOKIE_SESSION_KEY_GEO_DIARY) + yield call(redirectToLogout, process.env.COGNITO_CLIENT_ID_GEO_DIARY as string, `${window.location.origin}/login`) } catch (err) { console.error(err.message) } diff --git a/packages/geo-diary/src/sagas/next-appointment.ts b/packages/geo-diary/src/sagas/next-appointment.ts index b50a8be584..3eaeffbec6 100644 --- a/packages/geo-diary/src/sagas/next-appointment.ts +++ b/packages/geo-diary/src/sagas/next-appointment.ts @@ -77,10 +77,10 @@ export const validateNextAppointment = function*({ data: travelMode }: Action { - if (!attendee.communicationDetails) { + if (!attendee.mobilePhone) { return false } - return attendee.communicationDetails.findIndex(({ label }) => label === 'Mobile') > -1 + return attendee })[0] const currentNegotiator = getLoggedInUser(appointment.negotiators, userCode) diff --git a/packages/geo-diary/src/styles/pages/login.scss b/packages/geo-diary/src/styles/pages/login.scss index b3d8423b9d..062953cea6 100644 --- a/packages/geo-diary/src/styles/pages/login.scss +++ b/packages/geo-diary/src/styles/pages/login.scss @@ -25,10 +25,21 @@ } h1, - p { + p, img { text-align: center; } + img { + margin: 0 auto; + max-width: 200px; + display: block; + } + + button { + margin: 0 auto; + max-width: 400px; + } + @media screen and (max-width: 900px) { width: 100%; } diff --git a/packages/geo-diary/src/types/index.d.ts b/packages/geo-diary/src/types/index.d.ts index 6fce7b99dd..60d55717f9 100644 --- a/packages/geo-diary/src/types/index.d.ts +++ b/packages/geo-diary/src/types/index.d.ts @@ -11,5 +11,6 @@ declare module '*.scss' declare module '*.scss?mod' declare module '*.sass' declare module '*.jpg' +declare module '*.png' declare module 'swagger-ui-react' diff --git a/packages/geo-diary/src/utils/__stubs__/appointments.ts b/packages/geo-diary/src/utils/__stubs__/appointments.ts index 1f538c8fe7..ab9414bef1 100644 --- a/packages/geo-diary/src/utils/__stubs__/appointments.ts +++ b/packages/geo-diary/src/utils/__stubs__/appointments.ts @@ -32,7 +32,6 @@ export const appoinmentsStub: ExtendedAppointmentModel[] = [ line3: 'Old Haversham', line4: 'Milton Keynes', postcode: 'MK19 7DZ', - country: 'GB', geolocation: { latitude: 52.079532, longitude: -0.790871, @@ -58,7 +57,6 @@ export const appoinmentsStub: ExtendedAppointmentModel[] = [ line3: 'Old Haversham', line4: 'Milton Keynes', postcode: 'MK19 7DZ', - country: 'GB', geolocation: { latitude: 52.079532, longitude: -0.790871, diff --git a/packages/geo-diary/src/utils/__tests__/capitalizeFirstLetter.ts b/packages/geo-diary/src/utils/__tests__/capitalize-first-letter.ts similarity index 73% rename from packages/geo-diary/src/utils/__tests__/capitalizeFirstLetter.ts rename to packages/geo-diary/src/utils/__tests__/capitalize-first-letter.ts index c3867fd7e0..27d68bcd6a 100644 --- a/packages/geo-diary/src/utils/__tests__/capitalizeFirstLetter.ts +++ b/packages/geo-diary/src/utils/__tests__/capitalize-first-letter.ts @@ -1,4 +1,4 @@ -import { capitalizeFirstLetter } from '../capitalizeFirstLetter' +import { capitalizeFirstLetter } from '../capitalize-first-letter' describe('capitalizeFirstLetter', () => { it('runs correctly', () => { diff --git a/packages/geo-diary/src/utils/__tests__/login.ts b/packages/geo-diary/src/utils/__tests__/login.ts deleted file mode 100644 index 8b738a44f6..0000000000 --- a/packages/geo-diary/src/utils/__tests__/login.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { validate } from '@/utils/form/login' - -describe('login form utils', () => { - it('should return correct value', () => { - const inputs = [ - { email: '', password: '' }, - { email: 'abc@gmail.com', password: 'password' }, - ] - const outputs = [ - { email: 'Required', password: 'Required' }, - { email: undefined, password: undefined }, - ] - - for (let i = 0; i < inputs.length; i++) { - const result = validate(inputs[i]) - expect(result).toEqual(outputs[i]) - } - }) -}) diff --git a/packages/geo-diary/src/utils/__tests__/sortAppoinmentsByStartTime.ts b/packages/geo-diary/src/utils/__tests__/sort-appointments-by-start-time.ts similarity index 88% rename from packages/geo-diary/src/utils/__tests__/sortAppoinmentsByStartTime.ts rename to packages/geo-diary/src/utils/__tests__/sort-appointments-by-start-time.ts index c5baf5264a..dd473ded9a 100644 --- a/packages/geo-diary/src/utils/__tests__/sortAppoinmentsByStartTime.ts +++ b/packages/geo-diary/src/utils/__tests__/sort-appointments-by-start-time.ts @@ -1,4 +1,4 @@ -import { sortAppoinmentsByStartTime } from '../sortAppoinmentsByStartTime' +import { sortAppoinmentsByStartTime } from '../sort-appointments-by-start-time' import { appoinmentsStub } from '../__stubs__/appointments' describe('sortAppoinmentsByStartTime', () => { diff --git a/packages/geo-diary/src/utils/capitalizeFirstLetter.ts b/packages/geo-diary/src/utils/capitalize-first-letter.ts similarity index 100% rename from packages/geo-diary/src/utils/capitalizeFirstLetter.ts rename to packages/geo-diary/src/utils/capitalize-first-letter.ts diff --git a/packages/geo-diary/src/utils/form/login.ts b/packages/geo-diary/src/utils/form/login.ts deleted file mode 100644 index 3e882764d6..0000000000 --- a/packages/geo-diary/src/utils/form/login.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { isEmail } from '@reapit/elements' -import { LoginFormValues } from '@/components/pages/login' - -export interface LoginFormError { - email?: string - password?: string -} -export function validate(values: LoginFormValues) { - let errors = {} as LoginFormError - - if (!values.email) { - errors.email = 'Required' - } else if (!isEmail(values.email)) { - errors.email = 'Invalid email address' - } - - if (!values.password) { - errors.password = 'Required' - } - - return errors -} diff --git a/packages/geo-diary/src/utils/session.ts b/packages/geo-diary/src/utils/session.ts index 8c73649c7a..88a05c5fc7 100644 --- a/packages/geo-diary/src/utils/session.ts +++ b/packages/geo-diary/src/utils/session.ts @@ -1,11 +1,12 @@ import store from '@/core/store' import { authLoginSuccess, authLogout } from '@/actions/auth' import { getSession } from '@reapit/cognito-auth' +import { COOKIE_SESSION_KEY_GEO_DIARY } from '../constants/api' export const getAccessToken = async (): Promise => { const { loginSession, refreshSession } = store.state.auth - const session = await getSession(loginSession, refreshSession) + const session = await getSession(loginSession, refreshSession, COOKIE_SESSION_KEY_GEO_DIARY) if (session) { store.dispatch(authLoginSuccess(session)) diff --git a/packages/geo-diary/src/utils/sortAppoinmentsByStartTime.ts b/packages/geo-diary/src/utils/sort-appointments-by-start-time.ts similarity index 100% rename from packages/geo-diary/src/utils/sortAppoinmentsByStartTime.ts rename to packages/geo-diary/src/utils/sort-appointments-by-start-time.ts From 2fcfe7ef0f14be54f690b03afb3fb8d9a8abc0f7 Mon Sep 17 00:00:00 2001 From: Will McVay Date: Sun, 9 Feb 2020 13:34:39 +0000 Subject: [PATCH 2/3] fix: cognito auth tests pass --- packages/cognito-auth/jest.config.js | 2 +- .../src/session/remove-session.test.ts | 4 ++-- .../src/tests/badges/badge-branches.svg | 2 +- .../src/tests/badges/badge-functions.svg | 2 +- .../src/tests/badges/badge-lines.svg | 2 +- .../src/tests/badges/badge-statements.svg | 2 +- .../cognito-auth/src/utils/cognito.test.ts | 24 ++++++++++++++++++- .../geo-diary/src/sagas/__tests__/auth.ts | 12 ++++++---- .../src/tests/badges/badge-branches.svg | 2 +- .../src/tests/badges/badge-functions.svg | 2 +- .../src/tests/badges/badge-lines.svg | 2 +- .../src/tests/badges/badge-statements.svg | 2 +- 12 files changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/cognito-auth/jest.config.js b/packages/cognito-auth/jest.config.js index b7d6c4c51d..03faccfe8c 100644 --- a/packages/cognito-auth/jest.config.js +++ b/packages/cognito-auth/jest.config.js @@ -17,7 +17,7 @@ module.exports = { ], coverageThreshold: { global: { - branches: 92, + branches: 91, functions: 86, lines: 93, statements: 93, diff --git a/packages/cognito-auth/src/session/remove-session.test.ts b/packages/cognito-auth/src/session/remove-session.test.ts index 4ac84d10c0..e85709eca8 100644 --- a/packages/cognito-auth/src/session/remove-session.test.ts +++ b/packages/cognito-auth/src/session/remove-session.test.ts @@ -4,14 +4,14 @@ import { COOKIE_SESSION_KEY } from '../utils/cognito' describe('removeSession', () => { it('should remove a session cookie for a valid host', () => { - window.location.host = 'something.reapit.com' + window.location.hostname = 'something.reapit.com' hardtack.remove = jest.fn() removeSession() expect(hardtack.remove).toHaveBeenCalledWith(COOKIE_SESSION_KEY, { path: '/', - domain: window.location.host, + domain: window.location.hostname, }) }) }) diff --git a/packages/cognito-auth/src/tests/badges/badge-branches.svg b/packages/cognito-auth/src/tests/badges/badge-branches.svg index 0f320c793b..7cab273d9f 100644 --- a/packages/cognito-auth/src/tests/badges/badge-branches.svg +++ b/packages/cognito-auth/src/tests/badges/badge-branches.svg @@ -1 +1 @@ - Coverage:branchesCoverage:branches94.38%94.38% \ No newline at end of file + Coverage:branchesCoverage:branches91.23%91.23% \ No newline at end of file diff --git a/packages/cognito-auth/src/tests/badges/badge-functions.svg b/packages/cognito-auth/src/tests/badges/badge-functions.svg index ec6f70a3d0..de983b00ca 100644 --- a/packages/cognito-auth/src/tests/badges/badge-functions.svg +++ b/packages/cognito-auth/src/tests/badges/badge-functions.svg @@ -1 +1 @@ - Coverage:functionsCoverage:functions88.89%88.89% \ No newline at end of file + Coverage:functionsCoverage:functions89.36%89.36% \ No newline at end of file diff --git a/packages/cognito-auth/src/tests/badges/badge-lines.svg b/packages/cognito-auth/src/tests/badges/badge-lines.svg index bd7df9b9fc..546c48631f 100644 --- a/packages/cognito-auth/src/tests/badges/badge-lines.svg +++ b/packages/cognito-auth/src/tests/badges/badge-lines.svg @@ -1 +1 @@ - Coverage:linesCoverage:lines95.05%95.05% \ No newline at end of file + Coverage:linesCoverage:lines94.92%94.92% \ No newline at end of file diff --git a/packages/cognito-auth/src/tests/badges/badge-statements.svg b/packages/cognito-auth/src/tests/badges/badge-statements.svg index 1057e75c42..b875ce608b 100644 --- a/packages/cognito-auth/src/tests/badges/badge-statements.svg +++ b/packages/cognito-auth/src/tests/badges/badge-statements.svg @@ -1 +1 @@ - Coverage:statementsCoverage:statements95.11%95.11% \ No newline at end of file + Coverage:statementsCoverage:statements94.94%94.94% \ No newline at end of file diff --git a/packages/cognito-auth/src/utils/cognito.test.ts b/packages/cognito-auth/src/utils/cognito.test.ts index 2297a6e3d8..17d06b83a0 100644 --- a/packages/cognito-auth/src/utils/cognito.test.ts +++ b/packages/cognito-auth/src/utils/cognito.test.ts @@ -10,10 +10,12 @@ import { checkHasIdentityId, COOKIE_EXPIRY, redirectToOAuth, + redirectToLogin, } from './cognito' import { mockCognitoUserSession, mockLoginSession } from '../__mocks__/cognito-session' import hardtack from 'hardtack' import { LoginIdentity } from '../core/types' +import { redirectToLogout } from '@reapit/cognito-auth' jest.mock('amazon-cognito-identity-js', () => require('../__mocks__/cognito-session').mockCognito) @@ -55,7 +57,7 @@ describe('Session utils', () => { describe('setSessionCookie', () => { it('should set a refresh cookie', () => { - window.location.host = 'some.host' + window.location.hostname = 'some.host' hardtack.set = jest.fn() setSessionCookie(mockLoginSession) @@ -204,6 +206,26 @@ describe('Session utils', () => { }) }) + describe('redirectToLogin', () => { + it('should redirect to the OAuth endpoint for login', () => { + window.location.href = '' + process.env.COGNITO_OAUTH_URL = '' + redirectToLogin('cognitoClientId', 'redirectUri') + expect(window.location.href).toEqual( + '/login?response_type=code&client_id=cognitoClientId&redirect_uri=redirectUri', + ) + }) + }) + + describe('redirectToLogout', () => { + it('should redirect to the OAuth endpoint for logout', () => { + window.location.href = '' + process.env.COGNITO_OAUTH_URL = '' + redirectToLogout('cognitoClientId', 'redirectUri') + expect(window.location.href).toEqual('/logout?client_id=cognitoClientId&logout_uri=redirectUri') + }) + }) + afterEach(() => { jest.restoreAllMocks() }) diff --git a/packages/geo-diary/src/sagas/__tests__/auth.ts b/packages/geo-diary/src/sagas/__tests__/auth.ts index 9e63026a2a..3cfc9d4014 100644 --- a/packages/geo-diary/src/sagas/__tests__/auth.ts +++ b/packages/geo-diary/src/sagas/__tests__/auth.ts @@ -7,6 +7,7 @@ import { mockLoginSession } from '@/utils/__mocks__/session' import errorMessages from '@/constants/error-messages' import { LoginParams, setUserSession, removeSession, redirectToLogout } from '@reapit/cognito-auth' import store from '@/core/store' +import { COOKIE_SESSION_KEY_GEO_DIARY } from '../../constants/api' jest.mock('../../utils/session') jest.mock('@/core/store', () => ({ @@ -52,16 +53,17 @@ describe('auth sagas', () => { it('should redirect to login page', () => { const gen = doLogout() - expect(gen.next()).toEqual(call(store.purgeStore)) - - expect(gen.next()).toEqual(call(removeSession)) - gen.next() - expect(redirectToLogout).toHaveBeenCalledTimes(1) + expect(gen.next().value).toEqual(call(store.purgeStore)) + expect(gen.next().value).toEqual(call(removeSession, COOKIE_SESSION_KEY_GEO_DIARY)) + expect(gen.next().value).toEqual( + call(redirectToLogout, process.env.COGNITO_CLIENT_ID_GEO_DIARY as string, `${window.location.origin}/login`), + ) expect(gen.next().done).toBe(true) }) it('on logout fail', () => { const gen = doLogout() + expect(gen.next().value).toEqual(call(store.purgeStore)) gen.next(gen.throw(new Error(errorMessages.DEFAULT_SERVER_ERROR)).value) expect(gen.next().done).toBe(true) }) diff --git a/packages/geo-diary/src/tests/badges/badge-branches.svg b/packages/geo-diary/src/tests/badges/badge-branches.svg index 065b206c6b..0f83704f8d 100644 --- a/packages/geo-diary/src/tests/badges/badge-branches.svg +++ b/packages/geo-diary/src/tests/badges/badge-branches.svg @@ -1 +1 @@ - Coverage:branchesCoverage:branches66.85%66.85% \ No newline at end of file + Coverage:branchesCoverage:branches66.94%66.94% \ No newline at end of file diff --git a/packages/geo-diary/src/tests/badges/badge-functions.svg b/packages/geo-diary/src/tests/badges/badge-functions.svg index 546f0fd7e6..3db50a76b9 100644 --- a/packages/geo-diary/src/tests/badges/badge-functions.svg +++ b/packages/geo-diary/src/tests/badges/badge-functions.svg @@ -1 +1 @@ - Coverage:functionsCoverage:functions81.65%81.65% \ No newline at end of file + Coverage:functionsCoverage:functions81.07%81.07% \ No newline at end of file diff --git a/packages/geo-diary/src/tests/badges/badge-lines.svg b/packages/geo-diary/src/tests/badges/badge-lines.svg index 4f1785eec3..6ee6fb9f85 100644 --- a/packages/geo-diary/src/tests/badges/badge-lines.svg +++ b/packages/geo-diary/src/tests/badges/badge-lines.svg @@ -1 +1 @@ - Coverage:linesCoverage:lines92.01%92.01% \ No newline at end of file + Coverage:linesCoverage:lines91.76%91.76% \ No newline at end of file diff --git a/packages/geo-diary/src/tests/badges/badge-statements.svg b/packages/geo-diary/src/tests/badges/badge-statements.svg index 6f54e7c1ce..367c45dbde 100644 --- a/packages/geo-diary/src/tests/badges/badge-statements.svg +++ b/packages/geo-diary/src/tests/badges/badge-statements.svg @@ -1 +1 @@ - Coverage:statementsCoverage:statements91.08%91.08% \ No newline at end of file + Coverage:statementsCoverage:statements90.87%90.87% \ No newline at end of file From 95c2de51016e2f2760dfa1dd9359a10ded7b1b8a Mon Sep 17 00:00:00 2001 From: Will McVay Date: Sun, 9 Feb 2020 19:02:44 +0000 Subject: [PATCH 3/3] fix: oath flow applied to marketplace --- .vscode/launch.json | 21 --- packages/cognito-auth/jest.config.js | 2 +- .../services/session/refresh-user-session.ts | 5 +- .../src/session/refresh-user-session.ts | 4 +- .../src/tests/badges/badge-branches.svg | 2 +- .../cognito-auth/src/utils/cognito.test.ts | 16 +- packages/cognito-auth/src/utils/cognito.ts | 27 +++- .../__tests__/__snapshots__/login.tsx.snap | 4 +- .../geo-diary/src/components/pages/login.tsx | 8 +- .../src/assets/images/reapit-connect.png | Bin 0 -> 22433 bytes .../__tests__/__snapshots__/login.tsx.snap | 73 +++++----- .../src/components/pages/__tests__/login.tsx | 57 +------- .../src/components/pages/login.tsx | 137 +++++------------- packages/marketplace/src/constants/api.ts | 3 + .../src/core/private-route-wrapper.tsx | 12 +- packages/marketplace/src/reducers/auth.ts | 12 +- .../marketplace/src/sagas/__tests__/auth.ts | 17 ++- packages/marketplace/src/sagas/auth.ts | 14 +- .../marketplace/src/styles/pages/login.scss | 13 +- .../src/tests/badges/badge-branches.svg | 2 +- .../src/tests/badges/badge-functions.svg | 2 +- .../src/tests/badges/badge-lines.svg | 2 +- .../src/tests/badges/badge-statements.svg | 2 +- packages/marketplace/src/types/index.d.ts | 1 + packages/marketplace/src/utils/auth-route.ts | 6 +- .../src/utils/form/__tests__/login.ts | 36 ----- packages/marketplace/src/utils/form/login.ts | 22 --- packages/marketplace/src/utils/session.ts | 3 +- 28 files changed, 178 insertions(+), 325 deletions(-) delete mode 100644 .vscode/launch.json create mode 100644 packages/marketplace/src/assets/images/reapit-connect.png delete mode 100644 packages/marketplace/src/utils/form/__tests__/login.ts delete mode 100644 packages/marketplace/src/utils/form/login.ts diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 8c8d4d8c00..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": "0.1.0", - "configurations": [ - { - "name": "Attach to Chrome", - "port": 9222, - "request": "attach", - "type": "pwa-chrome", - "webRoot": "${workspaceFolder}" - }, - { - "type": "browser-preview", - "request": "launch", - "name": "Browser Preview: Launch", - "url": "http://localhost:8080", - "sourceMapPathOverrides": { - "webpack:///./*": "${workspaceRoot}/*" - } - } - ] -} diff --git a/packages/cognito-auth/jest.config.js b/packages/cognito-auth/jest.config.js index 03faccfe8c..c0023d1c33 100644 --- a/packages/cognito-auth/jest.config.js +++ b/packages/cognito-auth/jest.config.js @@ -17,7 +17,7 @@ module.exports = { ], coverageThreshold: { global: { - branches: 91, + branches: 88, functions: 86, lines: 93, statements: 93, diff --git a/packages/cognito-auth/src/services/session/refresh-user-session.ts b/packages/cognito-auth/src/services/session/refresh-user-session.ts index 12c9a8f014..547952e2f5 100644 --- a/packages/cognito-auth/src/services/session/refresh-user-session.ts +++ b/packages/cognito-auth/src/services/session/refresh-user-session.ts @@ -1,4 +1,4 @@ -import { LoginSession } from '../../core/types' +import { LoginSession, LoginType } from '../../core/types' import { getNewUser, getLoginSession } from '../../utils/cognito' import errorStrings from '../../constants/error-strings' import { fetcher } from '@reapit/elements' @@ -30,13 +30,14 @@ export const codeRefreshUserSessionService = async ( authorizationCode: string, redirectUri: string, congitoClientId: string, + loginType: LoginType = 'CLIENT', ): Promise> => { const session = await fetcher({ method: 'POST', api: process.env.COGNITO_OAUTH_URL as string, url: `/token?grant_type=authorization_code&client_id=${congitoClientId}` + - `&code=${authorizationCode}&redirect_uri=${redirectUri}`, + `&code=${authorizationCode}&redirect_uri=${redirectUri}&state=${loginType}`, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, diff --git a/packages/cognito-auth/src/session/refresh-user-session.ts b/packages/cognito-auth/src/session/refresh-user-session.ts index 6f67986e4a..5187d19e26 100644 --- a/packages/cognito-auth/src/session/refresh-user-session.ts +++ b/packages/cognito-auth/src/session/refresh-user-session.ts @@ -4,7 +4,7 @@ import { RefreshParams, LoginSession } from '../core/types' import { deserializeIdToken, checkHasIdentityId, setSessionCookie, COOKIE_SESSION_KEY } from '../utils/cognito' export const refreshUserSession = async (params: RefreshParams): Promise | undefined | void> => { - const { userName, refreshToken, cognitoClientId, authorizationCode, redirectUri } = params + const { userName, refreshToken, cognitoClientId, authorizationCode, redirectUri, loginType } = params try { if (userName && refreshToken && cognitoClientId) { @@ -12,7 +12,7 @@ export const refreshUserSession = async (params: RefreshParams): Promise Coverage:branchesCoverage:branches91.23%91.23% \ No newline at end of file + Coverage:branchesCoverage:branches88.98%88.98% \ No newline at end of file diff --git a/packages/cognito-auth/src/utils/cognito.test.ts b/packages/cognito-auth/src/utils/cognito.test.ts index 17d06b83a0..c7f572d20a 100644 --- a/packages/cognito-auth/src/utils/cognito.test.ts +++ b/packages/cognito-auth/src/utils/cognito.test.ts @@ -102,7 +102,7 @@ describe('Session utils', () => { describe('getTokenFromQueryString', () => { it('should correctly return RefreshParams for desktop mode', () => { - const validQuery = '?code=TOKEN&state=isDesktop' + const validQuery = '?code=TOKEN&state=DEVELOPER,DESKTOP' ;(window.location as any).origin = 'some.origin' const cognitoClientId = 'cognitoClientId' @@ -112,7 +112,7 @@ describe('Session utils', () => { cognitoClientId, authorizationCode: 'TOKEN', redirectUri: 'some.origin', - state: 'isDesktop', + state: 'DEVELOPER,DESKTOP', refreshToken: null, userName: null, }) @@ -199,9 +199,9 @@ describe('Session utils', () => { it('should redirect to the OAuth endpoint for authorize', () => { window.location.href = '' process.env.COGNITO_OAUTH_URL = '' - redirectToOAuth('cognitoClientId', 'redirectUri') + redirectToOAuth('cognitoClientId', 'redirectUri', 'DEVELOPER') expect(window.location.href).toEqual( - '/authorize?response_type=code&client_id=cognitoClientId&redirect_uri=redirectUri', + '/authorize?response_type=code&client_id=cognitoClientId&redirect_uri=redirectUri&state=DEVELOPER', ) }) }) @@ -210,9 +210,9 @@ describe('Session utils', () => { it('should redirect to the OAuth endpoint for login', () => { window.location.href = '' process.env.COGNITO_OAUTH_URL = '' - redirectToLogin('cognitoClientId', 'redirectUri') + redirectToLogin('cognitoClientId', 'redirectUri', 'DEVELOPER') expect(window.location.href).toEqual( - '/login?response_type=code&client_id=cognitoClientId&redirect_uri=redirectUri', + '/login?response_type=code&client_id=cognitoClientId&redirect_uri=redirectUri&state=DEVELOPER', ) }) }) @@ -221,8 +221,8 @@ describe('Session utils', () => { it('should redirect to the OAuth endpoint for logout', () => { window.location.href = '' process.env.COGNITO_OAUTH_URL = '' - redirectToLogout('cognitoClientId', 'redirectUri') - expect(window.location.href).toEqual('/logout?client_id=cognitoClientId&logout_uri=redirectUri') + redirectToLogout('cognitoClientId', 'redirectUri', 'DEVELOPER') + expect(window.location.href).toEqual('/logout?client_id=cognitoClientId&logout_uri=redirectUri&state=DEVELOPER') }) }) diff --git a/packages/cognito-auth/src/utils/cognito.ts b/packages/cognito-auth/src/utils/cognito.ts index ad1b78b8e0..27929d768c 100644 --- a/packages/cognito-auth/src/utils/cognito.ts +++ b/packages/cognito-auth/src/utils/cognito.ts @@ -71,7 +71,7 @@ export const getTokenFromQueryString = ( const params = new URLSearchParams(queryString) const authorizationCode = params.get('code') const state = params.get('state') - const mode = state && state.includes('') ? 'DESKTOP' : 'WEB' + const mode = state && state.includes('DESKTOP') ? 'DESKTOP' : 'WEB' if (authorizationCode) { return { @@ -117,19 +117,32 @@ export const checkHasIdentityId = (loginType: LoginType, loginIdentity: LoginIde (loginType === 'DEVELOPER' && !!loginIdentity.developerId) || (loginType === 'ADMIN' && !!loginIdentity.adminId) -export const redirectToOAuth = (congitoClientId: string, redirectUri: string = window.location.origin): void => { +export const redirectToOAuth = ( + congitoClientId: string, + redirectUri: string = window.location.origin, + loginType: LoginType = 'CLIENT', +): void => { window.location.href = `${process.env.COGNITO_OAUTH_URL}/authorize?` + - `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}` + `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}&state=${loginType}` } -export const redirectToLogin = (congitoClientId: string, redirectUri: string = window.location.origin): void => { +export const redirectToLogin = ( + congitoClientId: string, + redirectUri: string = window.location.origin, + loginType: LoginType = 'CLIENT', +): void => { window.location.href = `${process.env.COGNITO_OAUTH_URL}/login?` + - `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}` + `response_type=code&client_id=${congitoClientId}&redirect_uri=${redirectUri}&state=${loginType}` } -export const redirectToLogout = (congitoClientId: string, redirectUri: string = window.location.origin): void => { +export const redirectToLogout = ( + congitoClientId: string, + redirectUri: string = window.location.origin, + loginType: LoginType = 'CLIENT', +): void => { window.location.href = - `${process.env.COGNITO_OAUTH_URL}/logout?` + `client_id=${congitoClientId}&logout_uri=${redirectUri}` + `${process.env.COGNITO_OAUTH_URL}/logout?` + + `client_id=${congitoClientId}&logout_uri=${redirectUri}&state=${loginType}` } diff --git a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap index 418f9bf5cb..b50aedb62f 100644 --- a/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap +++ b/packages/geo-diary/src/components/pages/__tests__/__snapshots__/login.tsx.snap @@ -2,9 +2,7 @@ exports[`Login should match a snapshot 1`] = `
-
+
Reapit Connect Graphic void -} - export interface LoginProps { hasSession: boolean } @@ -30,7 +26,7 @@ export const Login: React.FunctionComponent = (props: LoginProps) => return (
-
+
Reapit Connect Graphic diff --git a/packages/marketplace/src/assets/images/reapit-connect.png b/packages/marketplace/src/assets/images/reapit-connect.png new file mode 100644 index 0000000000000000000000000000000000000000..207e7c5599305f3994bc2b0bf3de445c1c5c05c6 GIT binary patch literal 22433 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 XC7?3rOU}7+;d5h7+nAM^y2t$o$l(nb literal 0 HcmV?d00001 diff --git a/packages/marketplace/src/components/pages/__tests__/__snapshots__/login.tsx.snap b/packages/marketplace/src/components/pages/__tests__/__snapshots__/login.tsx.snap index 4ca1281262..435d170c47 100644 --- a/packages/marketplace/src/components/pages/__tests__/__snapshots__/login.tsx.snap +++ b/packages/marketplace/src/components/pages/__tests__/__snapshots__/login.tsx.snap @@ -2,13 +2,12 @@ exports[`Login should match a snapshot 1`] = `
-
- - Sign in +
+ + Reapit Connect Graphic

- - + - - + /> +

+ + + Login + +
{ expect(shallow()).toMatchSnapshot() }) - describe('handleUseEffect', () => { - it('should run correctly', () => { - const mockSetIsSubmitting = jest.fn() - const mockError = new Error('mock Error') - const fn = handleUseEffect({ setIsSubmitting: mockSetIsSubmitting, error: mockError }) - fn() - expect(mockSetIsSubmitting).toBeCalledWith(false) - }) - }) - - describe('onSubmitHandler', () => { - it('should run correctly', () => { - const mockSetIsSubmitting = jest.fn() - const mockLogin = jest.fn() - const loginType = 'ADMIN' - const mode = 'WEB' - const fn = onSubmitHandler({ setIsSubmitting: mockSetIsSubmitting, login: mockLogin, loginType, mode }) - const mockValues = { - email: '', - password: '', - } - fn(mockValues) - expect(mockSetIsSubmitting).toHaveBeenCalledWith(true) - expect(mockLogin).toHaveBeenCalledWith({ - email: mockValues.email, - mode, - loginType, - password: mockValues.password, - }) - }) - }) describe('mapStateToProps', () => { it('should return correctly', () => { const input = { auth: { loginSession: {}, - refreshSession: { - mode: 'WEB', - }, - error: {}, + refreshSession: {}, loginType: 'CLIENT', }, } as ReduxState const output = { hasSession: true, - error: {}, loginType: 'CLIENT', - mode: 'WEB', } const result = mapStateToProps(input) expect(result).toEqual(output) @@ -76,21 +38,6 @@ describe('Login', () => { }) describe('mapDispatchToProps', () => { - describe('login', () => { - it('should call dispatch correctly', () => { - const mockDispatch = jest.fn() - const { login } = mapDispatchToProps(mockDispatch) - const mockParams = { - userName: '', - password: '', - loginType: 'CLIENT', - mode: 'DESKTOP', - } as LoginParams - login(mockParams) - expect(mockDispatch).toBeCalled() - }) - }) - describe('authChangeLoginType', () => { it('should call dispatch correctly', () => { const mockDispatch = jest.fn() diff --git a/packages/marketplace/src/components/pages/login.tsx b/packages/marketplace/src/components/pages/login.tsx index 4909cf2ff7..113a0b1a98 100644 --- a/packages/marketplace/src/components/pages/login.tsx +++ b/packages/marketplace/src/components/pages/login.tsx @@ -1,34 +1,26 @@ import * as React from 'react' import { connect } from 'react-redux' -import { Formik, Form, Input, Button, Tabs, TabConfig, Alert, H1, Level } from '@reapit/elements' -import { LoginParams, LoginMode, LoginType } from '@reapit/cognito-auth' -import { Redirect, Link } from 'react-router-dom' +import { Button, Tabs, TabConfig, Level } from '@reapit/elements' +import { LoginType, redirectToLogin } from '@reapit/cognito-auth' +import { Redirect } from 'react-router-dom' import { ReduxState } from '../../types/core' -import { authLogin, authChangeLoginType } from '../../actions/auth' -import { validate } from '../../utils/form/login' +import { authChangeLoginType } from '../../actions/auth' import { Dispatch, compose } from 'redux' import Routes from '../../constants/routes' import loginStyles from '@/styles/pages/login.scss?mod' import { withRouter, RouteComponentProps } from 'react-router' import logoImage from '@/assets/images/reapit-graphic.jpg' -import { getLoginTypeByPath } from '@/utils/auth-route' +import { getLoginTypeByPath, getDefaultRouteByLoginType } from '@/utils/auth-route' import { getCookieString, COOKIE_FIRST_TIME_LOGIN } from '@/utils/cookie' +import connectImage from '@/assets/images/reapit-connect.png' export interface LoginMappedActions { - login: (params: LoginParams) => void authChangeLoginType: (loginType: string) => void } export interface LoginMappedProps { hasSession: boolean - error: boolean loginType: LoginType - mode: LoginMode -} - -export interface LoginFormValues { - userName: string - password: string } export type LoginProps = LoginMappedActions & LoginMappedProps & RouteComponentProps @@ -52,98 +44,48 @@ export const tabConfigs = ({ loginType, history }: LoginProps): TabConfig[] => [ }, ] -export const handleUseEffect = ({ setIsSubmitting, error }) => () => { - if (error) { - setIsSubmitting(false) - } -} - -export const onSubmitHandler = ({ setIsSubmitting, loginType, mode, login }) => values => { - setIsSubmitting(true) - login({ ...values, loginType, mode, cognitoClientId: process.env.COGNITO_CLIENT_ID_MARKETPLACE as string }) -} - export const Login: React.FunctionComponent = (props: LoginProps) => { - const [isSubmitting, setIsSubmitting] = React.useState(false) - const { hasSession, error, login, loginType, location, authChangeLoginType, mode } = props - const { disabled, wrapper, container, image /* , register */ } = loginStyles - - React.useEffect(handleUseEffect({ setIsSubmitting, error }), [error]) + const { hasSession, loginType, location, authChangeLoginType } = props + const { wrapper, container, image, tabsContainer /* , register */ } = loginStyles - let currentLoginType = getLoginTypeByPath(location.pathname) + const currentLoginType = getLoginTypeByPath(location.pathname) authChangeLoginType(currentLoginType) + const firstLoginCookie = getCookieString(COOKIE_FIRST_TIME_LOGIN) + const redirectRoute = getDefaultRouteByLoginType(loginType, firstLoginCookie) + if (hasSession) { - const firstLoginCookie = getCookieString(COOKIE_FIRST_TIME_LOGIN) - if (loginType === 'DEVELOPER' && !firstLoginCookie) { - return - } - if (loginType === 'DEVELOPER' && firstLoginCookie) { - return - } - return + return + } + const loginHandler = () => { + redirectToLogin(process.env.COGNITO_CLIENT_ID_MARKETPLACE as string, redirectRoute, loginType) } - - const queryParams = new URLSearchParams(props.location.search) - const isChangePasswordSuccess = queryParams.get('isChangePasswordSuccess') - const confirmError = queryParams.get('confirmError') return (
-
-

Sign in

+
+ + Reapit Connect Graphic +

Welcome to Reapit {`${loginType === 'CLIENT' ? 'Marketplace' : 'Foundations'}`}

- {loginType !== 'ADMIN' && } - - {() => ( -
- - {loginType === 'DEVELOPER' && ( -
- Forgot Password? -
- )} - - - {/* {loginType === 'DEVELOPER' && ( -
- Don't have an account yet?  - Register -
- )} */} - -
- {isChangePasswordSuccess && } - {error && } - {confirmError && ( - - )} - - )} -
+ + {loginType !== 'ADMIN' && ( +
+ +
+ )} + + + {/* {loginType === 'DEVELOPER' && ( +
+ Don't have an account yet?  + Register +
+ )} */} + +
Reapit Graphic @@ -154,13 +96,10 @@ export const Login: React.FunctionComponent = (props: LoginProps) => export const mapStateToProps = (state: ReduxState): LoginMappedProps => ({ hasSession: !!state.auth.loginSession || !!state.auth.refreshSession, - error: state.auth.error, loginType: state.auth.loginType, - mode: state?.auth?.refreshSession?.mode || 'WEB', }) export const mapDispatchToProps = (dispatch: Dispatch): LoginMappedActions => ({ - login: (params: LoginParams) => dispatch(authLogin(params)), authChangeLoginType: (loginType: string) => dispatch(authChangeLoginType(loginType as LoginType)), }) diff --git a/packages/marketplace/src/constants/api.ts b/packages/marketplace/src/constants/api.ts index 7a3b4ac584..54c1fc74ed 100644 --- a/packages/marketplace/src/constants/api.ts +++ b/packages/marketplace/src/constants/api.ts @@ -1,10 +1,13 @@ import { StringMap } from '../types/core' +import { COOKIE_SESSION_KEY } from '@reapit/cognito-auth' export const MARKETPLACE_HEADERS = { 'Content-Type': 'application/json', 'X-Api-Key': process.env.MARKETPLACE_API_KEY as string, } as StringMap +export const COOKIE_SESSION_KEY_MARKETPLACE = `${COOKIE_SESSION_KEY}-marketplace` + export const COGNITO_HEADERS = { 'Content-Type': 'application/json', } as StringMap diff --git a/packages/marketplace/src/core/private-route-wrapper.tsx b/packages/marketplace/src/core/private-route-wrapper.tsx index 82eb81007d..785c08a86c 100644 --- a/packages/marketplace/src/core/private-route-wrapper.tsx +++ b/packages/marketplace/src/core/private-route-wrapper.tsx @@ -9,6 +9,7 @@ import { Dispatch } from 'redux' import { withRouter } from 'react-router' import { authSetRefreshSession } from '../actions/auth' import { getDefaultRouteByLoginType } from '@/utils/auth-route' +import { getCookieString, COOKIE_FIRST_TIME_LOGIN } from '@/utils/cookie' const { Suspense } = React @@ -35,9 +36,14 @@ export const PrivateRouteWrapper: React.FunctionComponent { - const route = getDefaultRouteByLoginType(loginType) + const params = new URLSearchParams(location.search) + const state = params.get('state') + const type = + state && state.includes('ADMIN') ? 'ADMIN' : state && state.includes('DEVELOPER') ? 'DEVELOPER' : loginType + const firstLoginCookie = getCookieString(COOKIE_FIRST_TIME_LOGIN) + const route = getDefaultRouteByLoginType(type, firstLoginCookie) const cognitoClientId = process.env.COGNITO_CLIENT_ID_MARKETPLACE as string - const refreshParams = getTokenFromQueryString(location.search, cognitoClientId, loginType, route) + const refreshParams = getTokenFromQueryString(location.search, cognitoClientId, type, route) if (refreshParams && !hasSession) { setRefreshSession(refreshParams) @@ -46,7 +52,7 @@ export const PrivateRouteWrapper: React.FunctionComponent { + const refreshSession = getSessionCookie(COOKIE_SESSION_KEY_MARKETPLACE) return { error: false, loginSession: null, firstLogin: false, loginType: 'CLIENT', - refreshSession: null, + refreshSession, } } @@ -60,12 +62,16 @@ const authReducer = (state: AuthState = defaultState(), action: Action): Au } if (isType(action, authLogoutSuccess)) { - return defaultState() + return { + ...defaultState(), + refreshSession: null, + } } if (isType(action, authSetRefreshSession)) { return { ...state, + loginType: action.data.loginType, refreshSession: action.data, } } diff --git a/packages/marketplace/src/sagas/__tests__/auth.ts b/packages/marketplace/src/sagas/__tests__/auth.ts index 1b88ae6024..4b45dd781e 100644 --- a/packages/marketplace/src/sagas/__tests__/auth.ts +++ b/packages/marketplace/src/sagas/__tests__/auth.ts @@ -1,6 +1,6 @@ import MockDate from 'mockdate' import { put, all, takeLatest, call, fork } from '@redux-saga/core/effects' -import { setUserSession, removeSession, LoginParams, LoginSession } from '@reapit/cognito-auth' +import { setUserSession, removeSession, LoginParams, LoginSession, redirectToLogout } from '@reapit/cognito-auth' import { Action } from '@/types/core' import { cloneableGenerator } from '@redux-saga/testing-utils' import { getCookieString, setCookieString, COOKIE_FIRST_TIME_LOGIN } from '@/utils/cookie' @@ -18,9 +18,9 @@ import authSagas, { } from '../auth' import ActionTypes from '../../constants/action-types' import { authLoginSuccess, authLogoutSuccess, authLoginFailure, toggleFirstLogin } from '../../actions/auth' -import { history } from '../../core/router' import Routes from '../../constants/routes' import { ActionType } from '../../types/core' +import { COOKIE_SESSION_KEY_MARKETPLACE } from '../../constants/api' jest.mock('../../utils/session') jest.mock('../../core/router', () => ({ @@ -77,11 +77,14 @@ describe('auth thunks', () => { describe('authLogout', () => { it('should redirect to login page', () => { const gen = doLogout() - expect(gen.next().value).toEqual(call(removeSession)) - gen.next() - expect(history.push).toHaveBeenCalledTimes(1) - expect(history.push).toHaveBeenLastCalledWith(Routes.CLIENT_LOGIN) - expect(gen.next().value).toEqual(put(authLogoutSuccess())) + expect(gen.next().value).toEqual(call(removeSession, COOKIE_SESSION_KEY_MARKETPLACE)) + expect(gen.next().value).toEqual( + call( + redirectToLogout, + process.env.COGNITO_CLIENT_ID_MARKETPLACE as string, + `${window.location.origin}${Routes.CLIENT_LOGIN}`, + ), + ) expect(gen.next().done).toBe(true) }) }) diff --git a/packages/marketplace/src/sagas/auth.ts b/packages/marketplace/src/sagas/auth.ts index 79ac073c93..7b09fd3a33 100644 --- a/packages/marketplace/src/sagas/auth.ts +++ b/packages/marketplace/src/sagas/auth.ts @@ -2,11 +2,11 @@ import { takeLatest, put, call, all, fork } from '@redux-saga/core/effects' import ActionTypes from '../constants/action-types' import { authLoginSuccess, authLoginFailure, authLogoutSuccess, toggleFirstLogin } from '../actions/auth' import { Action } from '@/types/core.ts' -import { history } from '../core/router' -import { LoginSession, LoginParams, setUserSession, removeSession } from '@reapit/cognito-auth' +import { LoginSession, LoginParams, setUserSession, removeSession, redirectToLogout } from '@reapit/cognito-auth' import store from '../core/store' import { getAuthRouteByLoginType } from '@/utils/auth-route' import { getCookieString, setCookieString, COOKIE_FIRST_TIME_LOGIN } from '@/utils/cookie' +import { COOKIE_SESSION_KEY_MARKETPLACE } from '../constants/api' export const doLogin = function*({ data }: Action) { try { @@ -26,9 +26,13 @@ export const doLogout = function*() { try { const loginType = store?.state?.auth?.loginSession?.loginType || 'CLIENT' const authRoute = getAuthRouteByLoginType(loginType) - yield call(removeSession) - yield history.push(authRoute) - yield put(authLogoutSuccess()) + + yield call(removeSession, COOKIE_SESSION_KEY_MARKETPLACE) + yield call( + redirectToLogout, + process.env.COGNITO_CLIENT_ID_MARKETPLACE as string, + `${window.location.origin}${authRoute}`, + ) } catch (err) { console.error(err.message) } diff --git a/packages/marketplace/src/styles/pages/login.scss b/packages/marketplace/src/styles/pages/login.scss index 8034789f0a..ff61d6d40d 100644 --- a/packages/marketplace/src/styles/pages/login.scss +++ b/packages/marketplace/src/styles/pages/login.scss @@ -25,10 +25,21 @@ } h1, - p { + p, img { text-align: center; } + img { + margin: 0 auto; + max-width: 200px; + display: block; + } + + button, .tabsContainer { + margin: 0 auto 2rem auto; + max-width: 400px; + } + @media screen and (max-width: 900px) { width: 100%; } diff --git a/packages/marketplace/src/tests/badges/badge-branches.svg b/packages/marketplace/src/tests/badges/badge-branches.svg index 32f1903c5b..4c491e1a56 100644 --- a/packages/marketplace/src/tests/badges/badge-branches.svg +++ b/packages/marketplace/src/tests/badges/badge-branches.svg @@ -1 +1 @@ - Coverage:branchesCoverage:branches69.33%69.33% \ No newline at end of file + Coverage:branchesCoverage:branches69.33%69.33% diff --git a/packages/marketplace/src/tests/badges/badge-functions.svg b/packages/marketplace/src/tests/badges/badge-functions.svg index 3b3a188e68..7dbc465c9f 100644 --- a/packages/marketplace/src/tests/badges/badge-functions.svg +++ b/packages/marketplace/src/tests/badges/badge-functions.svg @@ -1 +1 @@ - Coverage:functionsCoverage:functions73.45%73.45% \ No newline at end of file + Coverage:functionsCoverage:functions73.45%73.45% diff --git a/packages/marketplace/src/tests/badges/badge-lines.svg b/packages/marketplace/src/tests/badges/badge-lines.svg index db5a139a95..2555e8e72a 100644 --- a/packages/marketplace/src/tests/badges/badge-lines.svg +++ b/packages/marketplace/src/tests/badges/badge-lines.svg @@ -1 +1 @@ - Coverage:linesCoverage:lines90.26%90.26% \ No newline at end of file + Coverage:linesCoverage:lines90.26%90.26% diff --git a/packages/marketplace/src/tests/badges/badge-statements.svg b/packages/marketplace/src/tests/badges/badge-statements.svg index 329daed713..e65e4525ae 100644 --- a/packages/marketplace/src/tests/badges/badge-statements.svg +++ b/packages/marketplace/src/tests/badges/badge-statements.svg @@ -1 +1 @@ - Coverage:statementsCoverage:statements89.35%89.35% \ No newline at end of file + Coverage:statementsCoverage:statements89.35%89.35% diff --git a/packages/marketplace/src/types/index.d.ts b/packages/marketplace/src/types/index.d.ts index 6fce7b99dd..60d55717f9 100644 --- a/packages/marketplace/src/types/index.d.ts +++ b/packages/marketplace/src/types/index.d.ts @@ -11,5 +11,6 @@ declare module '*.scss' declare module '*.scss?mod' declare module '*.sass' declare module '*.jpg' +declare module '*.png' declare module 'swagger-ui-react' diff --git a/packages/marketplace/src/utils/auth-route.ts b/packages/marketplace/src/utils/auth-route.ts index 0530b4122d..70ad66418f 100644 --- a/packages/marketplace/src/utils/auth-route.ts +++ b/packages/marketplace/src/utils/auth-route.ts @@ -12,12 +12,14 @@ export function getAuthRouteByLoginType(loginType: LoginType) { } } -export function getDefaultRouteByLoginType(loginType: LoginType) { +export function getDefaultRouteByLoginType(loginType: LoginType, firstLoginCookie?: string | undefined) { switch (loginType) { case 'ADMIN': return `${window.location.origin}${Routes.ADMIN_APPROVALS}` case 'DEVELOPER': - return `${window.location.origin}${Routes.DEVELOPER_MY_APPS}` + return !firstLoginCookie + ? `${window.location.origin}${Routes.DEVELOPER_WELCOME}` + : `${window.location.origin}${Routes.DEVELOPER_MY_APPS}` default: return `${window.location.origin}${Routes.INSTALLED_APPS}` } diff --git a/packages/marketplace/src/utils/form/__tests__/login.ts b/packages/marketplace/src/utils/form/__tests__/login.ts deleted file mode 100644 index 8f85ee85e1..0000000000 --- a/packages/marketplace/src/utils/form/__tests__/login.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { validate, LoginFormError } from '../login' -import { LoginFormValues } from '@/components/pages/login' - -type InputOutput = [LoginFormValues, LoginFormError] - -const invalidValues: InputOutput[] = [ - [ - { userName: '', password: '' }, - { userName: 'Required', password: 'Required' }, - ], - [ - { userName: 'invalid.com', password: '' }, - { userName: 'Invalid email address', password: 'Required' }, - ], - [{ userName: '', password: '12345678' }, { userName: 'Required' }], - [{ userName: '@@@.org', password: 'password' }, { userName: 'Invalid email address' }], -] - -const validValues: InputOutput[] = [ - [{ userName: 'my@gmail.com', password: '1234567' }, {}], - [{ userName: 'foo@bar.com.au', password: 'aaaaaaaaaa' }, {}], -] - -describe('loginValidation', () => { - it('invalid values', () => { - invalidValues.forEach(([input, output]) => { - expect(validate(input)).toEqual(output) - }) - }) - - it('valid values', () => { - validValues.forEach(([input, output]) => { - expect(validate(input)).toEqual(output) - }) - }) -}) diff --git a/packages/marketplace/src/utils/form/login.ts b/packages/marketplace/src/utils/form/login.ts deleted file mode 100644 index 6e2104af35..0000000000 --- a/packages/marketplace/src/utils/form/login.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { isEmail } from '../validate' -import { LoginFormValues } from '@/components/pages/login' - -export interface LoginFormError { - userName?: string - password?: string -} -export function validate(values: LoginFormValues) { - let errors = {} as LoginFormError - - if (!values.userName) { - errors.userName = 'Required' - } else if (!isEmail(values.userName)) { - errors.userName = 'Invalid email address' - } - - if (!values.password) { - errors.password = 'Required' - } - - return errors -} diff --git a/packages/marketplace/src/utils/session.ts b/packages/marketplace/src/utils/session.ts index bc2f30e14b..76388ea3e4 100644 --- a/packages/marketplace/src/utils/session.ts +++ b/packages/marketplace/src/utils/session.ts @@ -1,11 +1,12 @@ import store from '../core/store' import { authLoginSuccess, authLogout } from '../actions/auth' import { getSession } from '@reapit/cognito-auth' +import { COOKIE_SESSION_KEY_MARKETPLACE } from '../constants/api' export const getAccessToken = async (): Promise => { const { loginSession, refreshSession } = store.state.auth - const session = await getSession(loginSession, refreshSession) + const session = await getSession(loginSession, refreshSession, COOKIE_SESSION_KEY_MARKETPLACE) if (session) { store.dispatch(authLoginSuccess(session))