-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from archesproject/jtw/login-form
Add login interface #13
- Loading branch information
Showing
14 changed files
with
965 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
### Added | ||
- Add login interface [#13](https://github.com/archesproject/arches-lingo/issues/13) | ||
|
||
### Fixed | ||
|
||
### Deprecated | ||
|
||
### Removed | ||
|
||
### Security |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import LingoApp from '@/arches_lingo/App.vue'; | ||
import createVueApplication from 'arches/arches/app/media/js/utils/create-vue-application'; | ||
|
||
createVueApplication(LingoApp).then(vueApp => { | ||
vueApp.mount('#lingo-mounting-point'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<script setup lang="ts"> | ||
import Toast from "primevue/toast"; | ||
import ProgressSpinner from "primevue/progressspinner"; | ||
import PageSwitcher from "@/arches_lingo/pages/PageSwitcher.vue"; | ||
</script> | ||
|
||
<template> | ||
<Suspense> | ||
<PageSwitcher /> | ||
<template #fallback> | ||
<ProgressSpinner style="display: flex; margin-top: 8rem" /> | ||
</template> | ||
</Suspense> | ||
<Toast /> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import arches from "arches"; | ||
import Cookies from "js-cookie"; | ||
|
||
function getToken() { | ||
const token = Cookies.get("csrftoken"); | ||
if (!token) { | ||
throw new Error("Missing csrftoken"); | ||
} | ||
return token; | ||
} | ||
|
||
export const login = async (username: string, password: string) => { | ||
const response = await fetch(arches.urls.api_login, { | ||
method: "POST", | ||
headers: { "X-CSRFTOKEN": getToken() }, | ||
body: JSON.stringify({ username, password }), | ||
}); | ||
try { | ||
const parsed = await response.json(); | ||
if (response.ok) { | ||
return parsed; | ||
} | ||
throw new Error(parsed.message); | ||
} catch (error) { | ||
throw new Error((error as Error).message || response.statusText); | ||
} | ||
}; | ||
|
||
export const logout = async () => { | ||
const response = await fetch(arches.urls.api_logout, { | ||
method: "POST", | ||
headers: { "X-CSRFTOKEN": getToken() }, | ||
}); | ||
if (response.ok) { | ||
return true; | ||
} | ||
try { | ||
const error = await response.json(); | ||
throw new Error(error.message); | ||
} catch (error) { | ||
throw new Error((error as Error).message || response.statusText); | ||
} | ||
}; | ||
|
||
export const fetchUser = async () => { | ||
const response = await fetch(arches.urls.api_user); | ||
try { | ||
const parsed = await response.json(); | ||
if (response.ok) { | ||
return parsed; | ||
} | ||
throw new Error(parsed.message); | ||
} catch (error) { | ||
throw new Error((error as Error).message || response.statusText); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import type { InjectionKey } from "vue"; | ||
import type { UserRefAndSetter } from "@/arches_lingo/types"; | ||
|
||
export const ERROR = "error"; | ||
export const DEFAULT_ERROR_TOAST_LIFE = 8000; | ||
|
||
export const userKey = Symbol() as InjectionKey<UserRefAndSetter>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<script setup lang="ts"> | ||
import { computed, inject } from "vue"; | ||
import { useGettext } from "vue3-gettext"; | ||
import { useToast } from "primevue/usetoast"; | ||
import Button from "primevue/button"; | ||
import { DEFAULT_ERROR_TOAST_LIFE, ERROR } from "@/arches_lingo/constants.ts"; | ||
import { logout } from "@/arches_lingo/api.ts"; | ||
import { userKey } from "@/arches_lingo/constants.ts"; | ||
import type { UserRefAndSetter } from "@/arches_lingo/types"; | ||
const { user, setUser } = inject(userKey) as UserRefAndSetter; | ||
const { $gettext } = useGettext(); | ||
const toast = useToast(); | ||
const issueLogout = async () => { | ||
try { | ||
await logout(); | ||
setUser(null); | ||
} catch (error) { | ||
toast.add({ | ||
severity: ERROR, | ||
life: DEFAULT_ERROR_TOAST_LIFE, | ||
summary: $gettext("Sign out failed."), | ||
detail: error instanceof Error ? error.message : undefined, | ||
}); | ||
} | ||
}; | ||
const bestName = computed(() => { | ||
if (!user.value) { | ||
return ""; | ||
} | ||
// TODO: determine appropriate i18n for this. | ||
if (user.value.first_name && user.value.last_name) { | ||
return user.value.first_name + " " + user.value.last_name; | ||
} | ||
return user.value.username; | ||
}); | ||
</script> | ||
|
||
<template> | ||
<main> | ||
<div style="display: flex; justify-content: space-between"> | ||
<h1>{{ $gettext("LINGO") }}</h1> | ||
<span>{{ $gettext("Hello %{bestName}", { bestName }) }}</span> | ||
<Button @click="issueLogout"> | ||
{{ $gettext("Sign out") }} | ||
</Button> | ||
</div> | ||
</main> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<script setup lang="ts"> | ||
import { provide, ref } from "vue"; | ||
import { useGettext } from "vue3-gettext"; | ||
import { useToast } from "primevue/usetoast"; | ||
import { fetchUser } from "@/arches_lingo/api.ts"; | ||
import { | ||
DEFAULT_ERROR_TOAST_LIFE, | ||
ERROR, | ||
userKey, | ||
} from "@/arches_lingo/constants.ts"; | ||
import HomePage from "@/arches_lingo/pages/HomePage.vue"; | ||
import LoginPage from "@/arches_lingo/pages/login/LoginPage.vue"; | ||
import type { User } from "@/arches_lingo/types"; | ||
const { $gettext } = useGettext(); | ||
const toast = useToast(); | ||
const user = ref<User | null>(null); | ||
const setUser = (userToSet: User | null) => { | ||
user.value = userToSet; | ||
}; | ||
provide(userKey, { user, setUser }); | ||
try { | ||
setUser(await fetchUser()); | ||
} catch (error) { | ||
toast.add({ | ||
severity: ERROR, | ||
life: DEFAULT_ERROR_TOAST_LIFE, | ||
summary: $gettext("Login required"), // most likely case is inactive user | ||
detail: error instanceof Error ? error.message : undefined, | ||
}); | ||
} | ||
</script> | ||
|
||
<template> | ||
<div style="font-family: sans-serif"> | ||
<HomePage v-if="user && user.username !== 'anonymous'" /> | ||
<LoginPage v-else /> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<script setup lang="ts"> | ||
import LoginForm from "@/arches_lingo/pages/login/components/LoginForm.vue"; | ||
import LoginLinks from "@/arches_lingo/pages/login/components/LoginLinks.vue"; | ||
</script> | ||
|
||
<template> | ||
<div style="margin: 5%"> | ||
<LoginForm /> | ||
<div | ||
class="spacer" | ||
style="height: 10rem" | ||
></div> | ||
<LoginLinks /> | ||
</div> | ||
</template> |
78 changes: 78 additions & 0 deletions
78
arches_lingo/src/arches_lingo/pages/login/components/LoginForm.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<script setup lang="ts"> | ||
import { inject, ref } from "vue"; | ||
import { useGettext } from "vue3-gettext"; | ||
import { useToast } from "primevue/usetoast"; | ||
import InputText from "primevue/inputtext"; | ||
import Button from "primevue/button"; | ||
import { login } from "@/arches_lingo/api.ts"; | ||
import { | ||
DEFAULT_ERROR_TOAST_LIFE, | ||
ERROR, | ||
userKey, | ||
} from "@/arches_lingo/constants.ts"; | ||
import type { UserRefAndSetter } from "@/arches_lingo/types"; | ||
const { $gettext } = useGettext(); | ||
const toast = useToast(); | ||
const { setUser } = inject(userKey) as UserRefAndSetter; | ||
const username = ref(); | ||
const password = ref(); | ||
const submit = async () => { | ||
try { | ||
const userToSet = await login(username.value, password.value); | ||
setUser(userToSet); | ||
} catch (error) { | ||
toast.add({ | ||
severity: ERROR, | ||
life: DEFAULT_ERROR_TOAST_LIFE, | ||
summary: $gettext("Sign in failed."), | ||
detail: error instanceof Error ? error.message : undefined, | ||
}); | ||
} | ||
}; | ||
</script> | ||
|
||
<template> | ||
<form> | ||
<h1>{{ $gettext("LINGO") }}</h1> | ||
<h2>{{ $gettext("Vocabulary management powered by Arches.") }}</h2> | ||
<InputText | ||
v-model="username" | ||
:placeholder="$gettext('Username')" | ||
:aria-label="$gettext('Username')" | ||
autocomplete="username" | ||
/> | ||
<InputText | ||
v-model="password" | ||
:placeholder="$gettext('Password')" | ||
:aria-label="$gettext('Password')" | ||
type="password" | ||
autocomplete="password" | ||
@keyup.enter="submit" | ||
/> | ||
|
||
<Button | ||
type="button" | ||
:label="$gettext('Sign In')" | ||
@click="submit" | ||
/> | ||
</form> | ||
</template> | ||
|
||
<style scoped> | ||
form { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1rem; | ||
width: 30%; | ||
} | ||
input { | ||
width: 100%; | ||
} | ||
</style> |
23 changes: 23 additions & 0 deletions
23
arches_lingo/src/arches_lingo/pages/login/components/LoginLinks.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script setup lang="ts"> | ||
import arches from "arches"; | ||
import { useGettext } from "vue3-gettext"; | ||
import Button from "primevue/button"; | ||
const { $gettext } = useGettext(); | ||
</script> | ||
|
||
<template> | ||
<div style="display: flex; justify-content: space-between; width: 30%"> | ||
<Button | ||
as="a" | ||
:label="$gettext('Register')" | ||
:href="arches.urls.signup" | ||
/> | ||
<Button | ||
as="a" | ||
:label="$gettext('Multi-factor login')" | ||
:href="arches.urls.auth + '?next=/'" | ||
/> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Ref } from "vue"; | ||
|
||
export interface User { | ||
first_name: string; | ||
last_name: string; | ||
username: string; | ||
} | ||
|
||
// Prop injection types | ||
export interface UserRefAndSetter { | ||
user: Ref<User | null>; | ||
setUser: (userToSet: User | null) => void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.