Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: miscellaneous updates to signin #211

Merged
merged 12 commits into from Mar 20, 2024
8 changes: 6 additions & 2 deletions client/src/App.vue
@@ -1,14 +1,17 @@
<template>
<AuthWrapper>
<Header :logo-image-src="lockup" />
<router-view />
<ErrorBoundary component="main">
<router-view />
</ErrorBoundary>
<Footer :logo-image-src="acronym" />
</AuthWrapper>
</template>

<script>
import { Footer, Header } from 'pdap-design-system';
import AuthWrapper from './components/AuthWrapper.vue';
import ErrorBoundary from './components/ErrorBoundary.vue';
import acronym from 'pdap-design-system/images/acronym.svg';
import lockup from 'pdap-design-system/images/lockup.svg';

Expand All @@ -18,6 +21,7 @@ export default {
name: 'App',
components: {
AuthWrapper,
ErrorBoundary,
Header,
Footer,
},
Expand All @@ -40,6 +44,6 @@ export default {
}

main {
min-height: calc(100% - 80px - 500px);
min-height: calc(100vh - 80px - 500px);
}
</style>
37 changes: 37 additions & 0 deletions client/src/components/ErrorBoundary.vue
@@ -0,0 +1,37 @@
<template>
<component
:is="component"
v-if="error"
class="pdap-flex-container-center h-[full]"
>
<h1>Oops, something went wrong!</h1>
<p class="max-w-full" data-test="error-boundary-message">
If you keep seeing this message, please email
<a href="mailto:contact@pdap.io">contact@pdap.io</a> for assistance.
</p>
</component>
<slot v-else />
</template>
<script>
export default {
props: {
component: {
type: String,
default: 'div',
},
},
data() {
return {
error: false,
};
},
errorCaptured(error) {
this.interceptError(error);
},
methods: {
interceptError(error) {
this.error = error;
},
},
};
</script>
@@ -0,0 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ErrorBoundary > renders error content with error 1`] = `
<div class="pdap-flex-container-center h-[full]">
<h1>Oops, something went wrong!</h1>
<p class="max-w-full"> If you keep seeing this message, please email <a href="mailto:contact@pdap.io">contact@pdap.io</a> for assistance. </p>
</div>
`;

exports[`ErrorBoundary > renders slot content with no error 1`] = `<div></div>`;
1 change: 0 additions & 1 deletion client/src/components/__tests__/authWrapper.test.js
Expand Up @@ -14,7 +14,6 @@ const NOW_PLUS_THIRTY = NOW + 30 * 1000;
describe('AuthWrapper', () => {
beforeEach(() => {
wrapper = mount(AuthWrapper, {
// props: { dataSource },
global: {
plugins: [createTestingPinia()],
},
Expand Down
31 changes: 31 additions & 0 deletions client/src/components/__tests__/errorBoundary.test.js
@@ -0,0 +1,31 @@
import { mount } from '@vue/test-utils';
import { beforeEach, describe, expect, it } from 'vitest';
import ErrorBoundary from '../ErrorBoundary.vue';
import { nextTick } from 'vue';

let wrapper;

describe('ErrorBoundary', () => {
beforeEach(() => {
wrapper = mount(ErrorBoundary, {
slots: {
default: '<div data-test="default-slot" />',
},
});
});

it('renders slot content with no error', () => {
expect(wrapper.find('[data-test="default-slot"]').exists()).toBe(true);
expect(wrapper.html()).toMatchSnapshot();
});

it('renders error content with error', async () => {
wrapper.vm.interceptError(new Error('Generic error'));
await nextTick();

expect(wrapper.find('[data-test="error-boundary-message"]').exists()).toBe(
true,
);
expect(wrapper.html()).toMatchSnapshot();
});
});
2 changes: 1 addition & 1 deletion client/src/pages/LogIn.vue
Expand Up @@ -208,7 +208,7 @@ async function onSubmit(formValues) {

success.value = SUCCESS_COPY[type.value];
} catch (err) {
error.value = err.message;
error.value = 'Something went wrong, please try again.';
} finally {
loading.value = false;
}
Expand Down
15 changes: 15 additions & 0 deletions client/src/pages/NotFound.vue
@@ -0,0 +1,15 @@
<template>
<main class="p-4">
<h1>Something went wrong on our end</h1>
<p>
If you keep seeing this message, please email
<a href="mailto:contact@pdap.io">contact@pdap.io</a> for assistance.
</p>
</main>
</template>

<script>
export default {
name: 'NotFound',
};
</script>
2 changes: 1 addition & 1 deletion client/src/pages/ResetPassword.vue
Expand Up @@ -18,7 +18,7 @@
<p
v-if="isExpiredToken"
data-test="token-expired"
class="flex flex-col items-start sm:flex-row sm:items-center sm:gap-4"
class="flex flex-col items-start sm:gap-4"
>
Sorry, that token has expired.
<RouterLink
Expand Down
14 changes: 8 additions & 6 deletions client/src/pages/SearchResultPage.vue
Expand Up @@ -55,12 +55,12 @@
{{ section.header }}
</GridItem>

<SearchResultCard
v-for="record in section.records"
:key="record.type"
data-test="search-results-cards"
:data-source="searchResult[record.type]"
/>
<ErrorBoundary v-for="record in section.records" :key="record.type">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: ErrorBoundary can be added anywhere. For example, in this v-for, we can stop the error from bubbling all the way up the component tree and only render the error message in the single card that failed, so the user can still interact with the rest of the UI.

<SearchResultCard
data-test="search-results-cards"
:data-source="searchResult[record.type]"
/>
</ErrorBoundary>
</GridContainer>
</div>
</main>
Expand All @@ -69,6 +69,7 @@
<script>
import { Button, GridContainer, GridItem } from 'pdap-design-system';
import SearchResultCard from '../components/SearchResultCard.vue';
import ErrorBoundary from '../components/ErrorBoundary.vue';
import axios from 'axios';
import pluralize from '../util/pluralize';
import { SEARCH_RESULTS_UI_SHAPE } from '../util/pageData';
Expand All @@ -77,6 +78,7 @@ export default {
name: 'SearchResultPage',
components: {
Button,
ErrorBoundary,
SearchResultCard,
GridContainer,
GridItem,
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/__tests__/login.test.js
Expand Up @@ -96,7 +96,7 @@ describe('Login page', () => {

const error = form.find('.pdap-form-error-message');
expect(error.exists()).toBe(true);
expect(error.text()).toBe('foo');
expect(error.text()).toBe('Something went wrong, please try again.');
});
});

Expand Down
8 changes: 5 additions & 3 deletions client/src/router.js
Expand Up @@ -2,11 +2,12 @@ import { createWebHistory, createRouter } from 'vue-router';
import { useAuthStore } from './stores/auth';

import ChangePassword from './pages/ChangePassword.vue';
import DataSourceStaticView from '../src/pages/DataSourceStaticView.vue';
import DataSourceStaticView from './pages/DataSourceStaticView.vue';
import LogIn from './pages/LogIn.vue';
import QuickSearchPage from '../src/pages/QuickSearchPage.vue';
import QuickSearchPage from './pages/QuickSearchPage.vue';
import ResetPassword from './pages/ResetPassword.vue';
import SearchResultPage from '../src/pages/SearchResultPage.vue';
import SearchResultPage from './pages/SearchResultPage.vue';
import NotFound from './pages/NotFound.vue';

export const PRIVATE_ROUTES = ['/change-password'];

Expand Down Expand Up @@ -37,6 +38,7 @@ const routes = [
component: ResetPassword,
name: 'ResetPassword',
},
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
];

const router = createRouter({
Expand Down
22 changes: 9 additions & 13 deletions client/src/stores/auth.js
Expand Up @@ -24,21 +24,17 @@ export const useAuthStore = defineStore('auth', {
async login(email, password) {
const user = useUserStore();

try {
const response = await axios.post(
LOGIN_URL,
{ email, password },
HEADERS,
);
const response = await axios.post(
LOGIN_URL,
{ email, password },
HEADERS,
);

// Update user store with email
user.$patch({ email });
// Update user store with email
user.$patch({ email });

this.parseTokenAndSetData(response);
if (this.returnUrl) router.push(this.returnUrl);
} catch (error) {
throw new Error(error.response?.data?.message);
}
this.parseTokenAndSetData(response);
if (this.returnUrl) router.push(this.returnUrl);
},

logout(isAuthRoute) {
Expand Down
50 changes: 17 additions & 33 deletions client/src/stores/user.js
Expand Up @@ -19,50 +19,34 @@ export const useUserStore = defineStore('user', {
async signup(email, password) {
const auth = useAuthStore();

try {
await axios.post(SIGNUP_URL, { email, password }, HEADERS);
// Update store with email
this.$patch({ email });
// Log users in after signup and return that response
return await auth.login(email, password);
} catch (error) {
throw new Error(error.response?.data?.message);
}
await axios.post(SIGNUP_URL, { email, password }, HEADERS);
// Update store with email
this.$patch({ email });
// Log users in after signup and return that response
return await auth.login(email, password);
},

async changePassword(email, password) {
const auth = useAuthStore();
try {
await axios.put(
CHANGE_PASSWORD_URL,
{ email, password },
{
headers: {
...HEADERS.headers,
Authorization: `Bearer ${auth.accessToken.value}`,
},
await axios.put(
CHANGE_PASSWORD_URL,
{ email, password },
{
headers: {
...HEADERS.headers,
Authorization: `Bearer ${auth.accessToken.value}`,
},
);
return await auth.login(email, password);
} catch (error) {
throw new Error(error.response?.data?.message);
}
},
);
return await auth.login(email, password);
},

async requestPasswordReset(email) {
try {
await axios.post(REQUEST_PASSWORD_RESET_URL, { email }, HEADERS);
} catch (error) {
throw new Error(error.response?.data?.message);
}
await axios.post(REQUEST_PASSWORD_RESET_URL, { email }, HEADERS);
},

async resetPassword(password, token) {
try {
await axios.post(`${PASSWORD_RESET_URL}`, { password, token }, HEADERS);
} catch (error) {
throw new Error(error.response?.data?.message);
}
await axios.post(`${PASSWORD_RESET_URL}`, { password, token }, HEADERS);
},
},
});