Add settings page with change password form
This commit is contained in:
parent
e60032a405
commit
a9643a2425
9 changed files with 162 additions and 30 deletions
|
@ -240,6 +240,24 @@ export async function updateProfile(
|
|||
}
|
||||
}
|
||||
|
||||
export async function changePassword(
|
||||
authToken: string,
|
||||
newPassword: string,
|
||||
): Promise<User> {
|
||||
const url = `${BACKEND_URL}/api/v1/accounts/change_password`
|
||||
const response = await http(url, {
|
||||
method: "POST",
|
||||
json: { new_password: newPassword },
|
||||
authToken,
|
||||
})
|
||||
const data = await response.json()
|
||||
if (response.status !== 200) {
|
||||
throw new Error(data.message)
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
interface UnsignedActivity {
|
||||
internal_activity_id: string,
|
||||
activity: string,
|
||||
|
|
1
src/assets/feather/settings.svg
Normal file
1
src/assets/feather/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
After Width: | Height: | Size: 1,013 B |
|
@ -23,6 +23,10 @@
|
|||
<div class="icon"><img :src="require('@/assets/tabler/coin.svg')"></div>
|
||||
<span>Subscriptions</span>
|
||||
</router-link>
|
||||
<router-link class="sidebar-link" :to="{ name: 'settings' }">
|
||||
<div class="icon"><img :src="require('@/assets/feather/settings.svg')"></div>
|
||||
<span>Settings</span>
|
||||
</router-link>
|
||||
<router-link class="sidebar-link" to="/about">
|
||||
<div class="icon"><img :src="require('@/assets/feather/help-circle.svg')"></div>
|
||||
<span>About</span>
|
||||
|
|
|
@ -178,6 +178,12 @@ header {
|
|||
font-weight: bold;
|
||||
margin: 0 0 $block-outer-padding;
|
||||
}
|
||||
|
||||
:deep(h2) {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 $block-outer-padding;
|
||||
}
|
||||
}
|
||||
|
||||
#main.wide {
|
||||
|
|
|
@ -3,14 +3,15 @@ import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
|
|||
import AboutPage from "@/views/About.vue"
|
||||
import HomeTimeline from "@/views/HomeTimeline.vue"
|
||||
import IdentityProof from "@/views/IdentityProof.vue"
|
||||
import LandingPage from "../views/LandingPage.vue"
|
||||
import NotificationList from "../views/NotificationList.vue"
|
||||
import ProfileDirectory from "../views/ProfileDirectory.vue"
|
||||
import LandingPage from "@/views/LandingPage.vue"
|
||||
import NotificationList from "@/views/NotificationList.vue"
|
||||
import ProfileDirectory from "@/views/ProfileDirectory.vue"
|
||||
import ProfileView from "@/views/Profile.vue"
|
||||
import ProfileForm from "@/views/ProfileForm.vue"
|
||||
import PostDetail from "@/views/PostDetail.vue"
|
||||
import PostOverlay from "@/views/PostOverlay.vue"
|
||||
import PublicTimeline from "@/views/PublicTimeline.vue"
|
||||
import SettingsPage from "@/views/Settings.vue"
|
||||
import TagTimeline from "@/views/TagTimeline.vue"
|
||||
import SearchResultList from "@/views/SearchResultList.vue"
|
||||
import SubscriptionPage from "@/views/SubscriptionPage.vue"
|
||||
|
@ -117,6 +118,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||
{
|
||||
path: "/settings",
|
||||
name: "settings",
|
||||
component: SettingsPage,
|
||||
meta: { onlyAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: "/settings/profile",
|
||||
name: "settings-profile",
|
||||
component: ProfileForm,
|
||||
meta: { onlyAuthenticated: true },
|
||||
},
|
||||
|
|
|
@ -124,3 +124,28 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin content-form {
|
||||
.input-group {
|
||||
margin-bottom: $block-outer-padding;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: $input-padding;
|
||||
}
|
||||
|
||||
.sub-label {
|
||||
color: $secondary-text-color;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
border-radius: $btn-border-radius;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,13 @@
|
|||
<div class="actor-address">@{{ actorAddress }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<router-link v-if="isCurrentUser()" class="edit-profile btn" to="/settings">Edit profile</router-link>
|
||||
<router-link
|
||||
v-if="isCurrentUser()"
|
||||
class="edit-profile btn"
|
||||
:to="{ name: 'settings-profile' }"
|
||||
>
|
||||
Edit profile
|
||||
</router-link>
|
||||
<button v-if="canFollow()" class="follow btn" @click="onFollow()">Follow</button>
|
||||
<button v-if="canUnfollow()" class="unfollow btn" @click="onUnfollow()">
|
||||
<template v-if="isFollowRequestPending()">Cancel follow request</template>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<sidebar-layout>
|
||||
<template #content>
|
||||
<form @submit.prevent="save()">
|
||||
<h1>Edit profile</h1>
|
||||
<h1>Edit profile</h1>
|
||||
<form class="profile-form" @submit.prevent="save()">
|
||||
<div class="input-group">
|
||||
<label for="display-name">Display name</label>
|
||||
<input id="display-name" v-model.trim="form.display_name">
|
||||
|
@ -203,37 +203,19 @@ function isFormValid(): boolean {
|
|||
|
||||
async function save() {
|
||||
const authToken = ensureAuthToken()
|
||||
const profile = await updateProfile(authToken, form)
|
||||
setCurrentUser(profile)
|
||||
router.push({ name: "profile", params: { profileId: profile.id } })
|
||||
const user = await updateProfile(authToken, form)
|
||||
setCurrentUser(user)
|
||||
router.push({ name: "profile", params: { profileId: user.id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../styles/layout";
|
||||
@import "../styles/mixins";
|
||||
@import "../styles/theme";
|
||||
|
||||
.input-group {
|
||||
margin-bottom: $block-outer-padding;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: $input-padding;
|
||||
}
|
||||
|
||||
.sub-label {
|
||||
color: $secondary-text-color;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
border-radius: $btn-border-radius;
|
||||
width: 100%;
|
||||
}
|
||||
.profile-form {
|
||||
@include content-form;
|
||||
}
|
||||
|
||||
.image-upload-group {
|
||||
|
|
83
src/views/Settings.vue
Normal file
83
src/views/Settings.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<sidebar-layout>
|
||||
<template #content>
|
||||
<h1>Settings</h1>
|
||||
<section>
|
||||
<h2>Profile</h2>
|
||||
<router-link
|
||||
class="edit-profile btn"
|
||||
:to="{ name: 'settings-profile' }"
|
||||
>
|
||||
Edit profile
|
||||
</router-link>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Change password</h2>
|
||||
<form @submit.prevent="onChangePassword()">
|
||||
<div class="input-group">
|
||||
<label for="new-password">New password</label>
|
||||
<input id="new-password" type="password" v-model="newPassword">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="new-password-confirmation">New password (confirmation)</label>
|
||||
<input id="new-password-confirmation" type="password" v-model="newPasswordConfirmation">
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn"
|
||||
:disabled="!canChangePassword()"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<div class="password-form-message" v-if="passwordFormMessage">
|
||||
{{ passwordFormMessage }}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
</sidebar-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { $, $ref } from "vue/macros"
|
||||
|
||||
import { changePassword } from "@/api/users"
|
||||
import SidebarLayout from "@/components/SidebarLayout.vue"
|
||||
import { useCurrentUser } from "@/store/user"
|
||||
|
||||
const { ensureAuthToken, setCurrentUser } = $(useCurrentUser())
|
||||
let newPassword = $ref("")
|
||||
let newPasswordConfirmation = $ref("")
|
||||
let passwordFormMessage = $ref<string | null>(null)
|
||||
|
||||
function canChangePassword(): boolean {
|
||||
return newPassword && newPassword === newPasswordConfirmation
|
||||
}
|
||||
|
||||
async function onChangePassword() {
|
||||
const authToken = ensureAuthToken()
|
||||
const user = await changePassword(authToken, newPassword)
|
||||
setCurrentUser(user)
|
||||
newPassword = ""
|
||||
newPasswordConfirmation = ""
|
||||
passwordFormMessage = "Password changed"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../styles/layout";
|
||||
@import "../styles/mixins";
|
||||
@import "../styles/theme";
|
||||
|
||||
section {
|
||||
margin-bottom: 3 * $block-outer-padding;
|
||||
}
|
||||
|
||||
form {
|
||||
@include content-form;
|
||||
}
|
||||
|
||||
.password-form-message {
|
||||
margin-top: $block-outer-padding;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue