feat: UI of shortcode actions (#2347)

Co-authored-by: liyasthomas <liyascthomas@gmail.com>
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
Nivedin
2022-05-24 17:58:49 +05:30
committed by GitHub
parent 184914ba4f
commit cfa89a6ded
12 changed files with 632 additions and 119 deletions

View File

@@ -3,7 +3,13 @@
<div class="container">
<div class="p-4">
<div
v-if="currentUser === null"
v-if="loadingCurrentUser"
class="flex flex-col items-center justify-center flex-1 p-4"
>
<SmartSpinner class="mb-4" />
</div>
<div
v-else-if="currentUser === null"
class="flex flex-col items-center justify-center"
>
<img
@@ -79,102 +85,195 @@
</div>
<SmartTabs v-model="selectedProfileTab">
<SmartTab :id="'sync'" :label="t('settings.account')">
<section class="p-4">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.profile") }}
</h4>
<div class="my-1 text-secondaryLight">
{{ t("settings.profile_description") }}
</div>
<div class="py-4">
<label for="displayName">
{{ t("settings.profile_name") }}
</label>
<form
class="flex mt-2 md:max-w-sm"
@submit.prevent="updateDisplayName"
>
<input
id="displayName"
v-model="displayName"
class="input"
:placeholder="`${t('settings.profile_name')}`"
type="text"
autocomplete="off"
required
/>
<ButtonSecondary
filled
outline
:label="t('action.save')"
class="ml-2 min-w-16"
type="submit"
:loading="updatingDisplayName"
/>
</form>
</div>
<div class="py-4">
<label for="emailAddress">
{{ t("settings.profile_email") }}
</label>
<form
class="flex mt-2 md:max-w-sm"
@submit.prevent="updateEmailAddress"
>
<input
id="emailAddress"
v-model="emailAddress"
class="input"
:placeholder="`${t('settings.profile_name')}`"
type="email"
autocomplete="off"
required
/>
<ButtonSecondary
filled
outline
:label="t('action.save')"
class="ml-2 min-w-16"
type="submit"
:loading="updatingEmailAddress"
/>
</form>
</div>
</section>
<section class="p-4">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.sync") }}
</h4>
<div class="my-1 text-secondaryLight">
{{ t("settings.sync_description") }}
</div>
<div class="py-4 space-y-4">
<div class="flex items-center">
<SmartToggle
:on="SYNC_COLLECTIONS"
@change="toggleSetting('syncCollections')"
>
{{ t("settings.sync_collections") }}
</SmartToggle>
<div class="grid grid-cols-1">
<section class="p-4">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.profile") }}
</h4>
<div class="my-1 text-secondaryLight">
{{ t("settings.profile_description") }}
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_ENVIRONMENTS"
@change="toggleSetting('syncEnvironments')"
<div class="py-4">
<label for="displayName">
{{ t("settings.profile_name") }}
</label>
<form
class="flex mt-2 md:max-w-sm"
@submit.prevent="updateDisplayName"
>
{{ t("settings.sync_environments") }}
</SmartToggle>
<input
id="displayName"
v-model="displayName"
class="input"
:placeholder="`${t('settings.profile_name')}`"
type="text"
autocomplete="off"
required
/>
<ButtonSecondary
filled
outline
:label="t('action.save')"
class="ml-2 min-w-16"
type="submit"
:loading="updatingDisplayName"
/>
</form>
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_HISTORY"
@change="toggleSetting('syncHistory')"
<div class="py-4">
<label for="emailAddress">
{{ t("settings.profile_email") }}
</label>
<form
class="flex mt-2 md:max-w-sm"
@submit.prevent="updateEmailAddress"
>
{{ t("settings.sync_history") }}
</SmartToggle>
<input
id="emailAddress"
v-model="emailAddress"
class="input"
:placeholder="`${t('settings.profile_name')}`"
type="email"
autocomplete="off"
required
/>
<ButtonSecondary
filled
outline
:label="t('action.save')"
class="ml-2 min-w-16"
type="submit"
:loading="updatingEmailAddress"
/>
</form>
</div>
</div>
</section>
</section>
<section class="p-4">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.sync") }}
</h4>
<div class="my-1 text-secondaryLight">
{{ t("settings.sync_description") }}
</div>
<div class="py-4 space-y-4">
<div class="flex items-center">
<SmartToggle
:on="SYNC_COLLECTIONS"
@change="toggleSetting('syncCollections')"
>
{{ t("settings.sync_collections") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_ENVIRONMENTS"
@change="toggleSetting('syncEnvironments')"
>
{{ t("settings.sync_environments") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_HISTORY"
@change="toggleSetting('syncHistory')"
>
{{ t("settings.sync_history") }}
</SmartToggle>
</div>
</div>
</section>
<section class="p-4">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.short_codes") }}
</h4>
<div class="my-1 text-secondaryLight">
{{ t("settings.short_codes_description") }}
</div>
<div class="relative py-4 overflow-x-auto hide-scrollbar">
<div
v-if="loading"
class="flex flex-col items-center justify-center"
>
<SmartSpinner class="mb-4" />
<span class="text-secondaryLight">{{
t("state.loading")
}}</span>
</div>
<div
v-if="!loading && myShortcodes.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
:src="`/images/states/${$colorMode.value}/add_files.svg`"
loading="lazy"
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-8"
:alt="`${t('empty.shortcodes')}`"
/>
<span class="mb-4 text-center">
{{ t("empty.shortcodes") }}
</span>
</div>
<div
v-else-if="!loading"
class="table w-full border-collapse table-auto"
>
<div
class="bg-primaryLight hidden lg:flex rounded-t w-full"
>
<div class="flex w-full">
<div class="w-1/5 p-3 font-semibold">
{{ t("shortcodes.short_code") }}
</div>
<div class="w-1/5 p-3 font-semibold">
{{ t("shortcodes.method") }}
</div>
<div class="w-3/5 p-3 font-semibold">
{{ t("shortcodes.url") }}
</div>
<div class="w-1/5 p-3 font-semibold">
{{ t("shortcodes.created_on") }}
</div>
<div class="w-1/5 p-3 font-semibold text-center">
{{ t("shortcodes.actions") }}
</div>
</div>
</div>
<div
class="w-full max-h-sm flex flex-col items-center justify-between overflow-y-scroll rounded lg:rounded-t-none border lg:divide-y border-dividerLight divide-dividerLight"
>
<div
class="flex flex-col h-auto h-full border-r border-dividerLight w-full"
>
<ProfileShortcode
v-for="(shortcode, shortcodeIndex) in myShortcodes"
:key="`shortcode-${shortcodeIndex}`"
:shortcode="shortcode"
@delete-shortcode="deleteShortcode"
/>
<SmartIntersection
v-if="hasMoreShortcodes && myShortcodes.length > 0"
@intersecting="loadMoreShortcodes()"
>
<div
v-if="adapterLoading"
class="flex flex-col items-center py-3"
>
<SmartSpinner />
</div>
</SmartIntersection>
</div>
</div>
</div>
<div
v-if="!loading && adapterError"
class="flex flex-col items-center py-4"
>
<i class="mb-4 material-icons">help_outline</i>
{{ getErrorMessage(adapterError) }}
</div>
</div>
</section>
</div>
</SmartTab>
<SmartTab :id="'teams'" :label="t('team.title')">
<Teams :modal="false" />
@@ -193,12 +292,18 @@ import {
useMeta,
defineComponent,
watchEffect,
computed,
} from "@nuxtjs/composition-api"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/TaskEither"
import { GQLError } from "~/helpers/backend/GQLClient"
import {
currentUser$,
probableUser$,
setDisplayName,
setEmailAddress,
verifyEmailAddress,
onLoggedIn,
} from "~/helpers/fb/auth"
import {
useReadonlyStream,
@@ -206,6 +311,8 @@ import {
useToast,
} from "~/helpers/utils/composables"
import { toggleSetting, useSetting } from "~/newstore/settings"
import ShortcodeListAdapter from "~/helpers/shortcodes/ShortcodeListAdapter"
import { deleteShortcode as backendDeleteShortcode } from "~/helpers/backend/mutations/Shortcode"
type ProfileTabs = "sync" | "teams"
@@ -220,6 +327,13 @@ const SYNC_COLLECTIONS = useSetting("syncCollections")
const SYNC_ENVIRONMENTS = useSetting("syncEnvironments")
const SYNC_HISTORY = useSetting("syncHistory")
const currentUser = useReadonlyStream(currentUser$, null)
const probableUser = useReadonlyStream(probableUser$, null)
const loadingCurrentUser = computed(() => {
if (!probableUser.value) return false
else if (!currentUser.value) return true
else return false
})
const displayName = ref(currentUser.value?.displayName)
const updatingDisplayName = ref(false)
@@ -273,6 +387,51 @@ const sendEmailVerification = () => {
})
}
const adapter = new ShortcodeListAdapter(true)
const adapterLoading = useReadonlyStream(adapter.loading$, false)
const adapterError = useReadonlyStream(adapter.error$, null)
const myShortcodes = useReadonlyStream(adapter.shortcodes$, [])
const hasMoreShortcodes = useReadonlyStream(adapter.hasMoreShortcodes$, true)
const loading = computed(
() => adapterLoading.value && myShortcodes.value.length === 0
)
onLoggedIn(() => {
adapter.initialize()
})
const deleteShortcode = (codeID: string) => {
pipe(
backendDeleteShortcode(codeID),
TE.match(
(err: GQLError<string>) => {
toast.error(`${getErrorMessage(err)}`)
},
() => {
toast.success(`${t("shortcodes.deleted")}`)
}
)
)()
}
const loadMoreShortcodes = () => {
adapter.loadMore()
}
const getErrorMessage = (err: GQLError<string>) => {
if (err.type === "network_error") {
return t("error.network_error")
} else {
switch (err.error) {
case "shortcode/not_found":
return t("shortcodes.not_found")
default:
return t("error.something_went_wrong")
}
}
}
useMeta({
title: `${t("navigation.profile")} • Hoppscotch`,
})