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

Strr2 - Draft Terms of Service #260

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions strr-platform-web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NUXT_REGISTRY_HOME_URL="https://dev.bcregistry.gov.bc.ca/"
NUXT_PAYMENT_PORTAL_URL="https://dev.account.bcregistry.gov.bc.ca/makepayment/"
NUXT_AUTH_WEB_URL="https://dev.account.bcregistry.gov.bc.ca/"
NUXT_BASE_URL="http://localhost:3000/" # app base url
NUXT_HOUSING_STRR_URL="" # TODO: add housing strr base url here for redirects
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you want to add that to https://github.com/bcgov/STRR/blob/main/strr-platform-web/devops/vaults.env
or I can in another PR?


#vaults keycloak
NUXT_KEYCLOAK_AUTH_URL="https://dev.loginproxy.gov.bc.ca/auth"
Expand Down
16 changes: 16 additions & 0 deletions strr-platform-web/app/composables/useStrrModals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const useStrrModals = () => {
const modal = useModal()
const { t } = useI18n()
const connectNav = useConnectNav()
const config = useRuntimeConfig().public

function openAppSubmitError (e: any) {
modal.open(ModalBase, {
Expand Down Expand Up @@ -37,13 +38,28 @@ export const useStrrModals = () => {
})
}

function openConfirmDeclineTosModal () {
modal.open(ModalBase, {
title: 'Decline Terms of Use?',
content: 'By declining the Terms of Use, you won’t be able to access this service. Do you wish to proceed?',
actions: [
{ label: t('btn.cancel'), variant: 'outline', handler: () => close() },
{
label: 'Decline Terms of Use',
handler: () => navigateTo(config.declineTosRedirectUrl as string, { external: true })
}
]
})
}

function close () {
modal.close()
}

return {
openAppSubmitError,
openCreateAccountModal,
openConfirmDeclineTosModal,
close
}
}
16 changes: 14 additions & 2 deletions strr-platform-web/app/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
export default defineNuxtRouteMiddleware(() => {
export default defineNuxtRouteMiddleware(async (to) => {
const { isAuthenticated } = useKeycloak()
const tosStore = useTosStore()
const localePath = useLocalePath()

if (!isAuthenticated.value) {
return navigateTo({ path: useLocalePath()('/auth/login') })
return navigateTo(localePath('/auth/login'))
}

// check if tos exists or is not accepted
if (tosStore.tos.isTermsOfUseAccepted === undefined || !tosStore.tos.isTermsOfUseAccepted) {
await tosStore.getTermsOfUse() // load latest tos if no tos found in store or not accepted

// return to tos page if not accepted
if (!tosStore.tos.isTermsOfUseAccepted) {
return navigateTo({ path: localePath('/auth/tos'), query: { return: to.fullPath } })
}
}
})
127 changes: 127 additions & 0 deletions strr-platform-web/app/pages/auth/tos.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<script setup lang="ts">
import type { FormError } from '#ui/types'
const { $sanitize } = useNuxtApp()
const { t } = useI18n()
const tosStore = useTosStore()
const strrModal = useStrrModals()
const route = useRoute()

// page stuff
useHead({
title: t('strr.title.login')
})

setBreadcrumbs([
{ label: t('label.bcregDash'), to: useRuntimeConfig().public.registryHomeURL + 'dashboard' },
{ label: 'Terms of Use' }
])

const checkboxRef = ref(null)
const formRef = ref() // typing not working here
const tosDivRef = ref<HTMLDivElement | null>(null)

const { bottom: tosBottom } = useElementBounding(tosDivRef)
const { top: formTop } = useElementBounding(formRef)

// track if user has scrolled to bottom of page
const hasReachedBottom = computed(() => formTop.value >= tosBottom.value)

// reset form errors if user reaches bottom of page
watch(hasReachedBottom, (newVal) => {
if (newVal) {
formRef.value?.clear()
}
})

const state = reactive({
agreeToTerms: undefined
})

const validate = (state: { agreeToTerms: boolean | undefined }): FormError[] => {
const errors: FormError[] = []

if (!state.agreeToTerms && !hasReachedBottom.value) {
errors.push({ path: 'agreeToTerms', message: 'You must scroll to the bottom of this page to accept the tos' })
return errors
}

if (!state.agreeToTerms) {
errors.push({ path: 'agreeToTerms', message: 'You must accept the Terms of Use to continue' })
}

return errors
}

async function submitTermsOfUse () {
try {
tosStore.loading = true
await tosStore.patchTermsOfUse()
await navigateTo(route.query.return as string)
} catch {
// TODO: handle patch errors
} finally {
tosStore.loading = false
}
}
</script>
<template>
<!-- eslint-disable vue/no-v-html -->
<div class="relative mx-auto flex w-full flex-col items-center sm:max-w-screen-sm md:max-w-screen-md">
<ConnectTypographyH1
class="sticky top-0 w-full border-b border-bcGovGray-500 bg-bcGovColor-gray1 pb-2 pt-4 text-center sm:pt-8"
text="Terms of Use"
/>

<div
v-if="tosStore.tos.termsOfUse"
ref="tosDivRef"
class="prose prose-bcGov max-w-full break-words"
v-html="$sanitize(tosStore.tos.termsOfUse)"
/>

<!-- TODO: display fallback content if tos fails to load -->

<UForm
ref="formRef"
class="sticky bottom-0 flex w-full flex-col items-start justify-between
gap-4 border-t border-bcGovGray-500 bg-bcGovColor-gray1 py-4 sm:flex-row sm:items-center sm:gap-0 sm:pb-8"
:state
:validate="validate"
@submit="submitTermsOfUse()"
>
<UFormGroup
name="agreeToTerms"
>
<UCheckbox
ref="checkboxRef"
v-model="state.agreeToTerms"
:disabled="!hasReachedBottom || tosStore.loading"
label="I have read and accept the Terms of Use"
/>
<template #error="{ error }">
<span :class="{ 'text-red-500': error, 'text-base': !hasReachedBottom }">
{{ error }}
</span>
</template>
</UFormGroup>
<div class="flex w-full gap-4 sm:w-fit">
<UButton
class="flex-1 sm:flex-none"
:ui="{ base: 'flex justify-center items-center'}"
label="Accept Terms of Use"
aria-label="Accept Terms of Use, You must scroll to the bottom to accept the terms of use checkbox"
type="submit"
:loading="tosStore.loading"
/>
<UButton
class="flex-1 sm:flex-none"
:ui="{ base: 'flex justify-center items-center'}"
:label="$t('btn.decline')"
variant="outline"
:disabled="tosStore.loading"
@click="strrModal.openConfirmDeclineTosModal()"
/>
</div>
</UForm>
</div>
</template>
63 changes: 63 additions & 0 deletions strr-platform-web/app/stores/terms-of-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
interface TOSPatchResponse {
isTermsOfUseAccepted: boolean
termsOfUseAcceptedVersion: string
}

interface TOSGetResponse {
isTermsOfUseAccepted: boolean
termsOfUseAcceptedVersion: string | null
termsOfUseCurrentVersion: string
termsOfUse: string
}

// TODO: make this generic for the core layer?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I think this would be good there. Might be good to prefix this with 'connect' if we're going to do that. For now we could put it in the strr base layer and figure out the kinks before moving it

// will require the following api calls
// await $authApi('/users/@me') <-- this is breaking for Sergey, will need to fix this before adding to core layer
// await $authApi('/documents/termsofuse')

export const useTosStore = defineStore('strr/terms-of-service', () => {
const { $strrApi } = useNuxtApp()
const loading = ref<boolean>(false)
const tos = ref<TOSGetResponse>({} as TOSGetResponse)

async function getTermsOfUse ():Promise<void> {
try {
loading.value = true
tos.value = await $strrApi<TOSGetResponse>('/users/tos')
} catch {
// TODO: handle errors
} finally {
loading.value = false
}
}

// form submit event
async function patchTermsOfUse () {
const res = await $strrApi<TOSPatchResponse>('/users/tos', {
method: 'PATCH',
body: {
istermsaccepted: true,
termsversion: tos.value.termsOfUseCurrentVersion
}
})

// update tos accepted to match patch response or middleware will run again
tos.value.isTermsOfUseAccepted = res.isTermsOfUseAccepted
}

function $reset () {
sessionStorage.removeItem('strr/terms-of-service') // required to reset store on logout
loading.value = false
tos.value = {} as TOSGetResponse
}

return {
loading,
tos,
getTermsOfUse,
patchTermsOfUse,
$reset
}
},
{ persist: true } // this will persist in session storage so we only load the tos once per session
)
5 changes: 4 additions & 1 deletion strr-platform-web/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ export default defineNuxtConfig({
paymentPortalUrl: process.env.NUXT_PAYMENT_PORTAL_URL,
baseUrl: process.env.NUXT_BASE_URL,
environment: process.env.NUXT_ENVIRONMENT_HEADER || '',
version: `STRR Platform UI v${process.env.npm_package_version}`
version: `STRR Platform UI v${process.env.npm_package_version}`,
housingStrrUrl: process.env.NUXT_REGISTRY_HOME_URL, // TODO: update to NUXT_HOUSING_STRR_URL once we get the housing strr url set
// TODO: move to app config for core layer ?
declineTosRedirectUrl: process.env.NUXT_REGISTRY_HOME_URL // TODO: update to NUXT_HOUSING_STRR_URL once we get the housing strr url set
// set by layer - still required in .env
// keycloakAuthUrl - NUXT_KEYCLOAK_AUTH_URL
// keycloakClientId - NUXT_KEYCLOAK_CLIENTID
Expand Down