Compare commits
2 Commits
fix/magic-
...
feat/env-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37153b6401 | ||
|
|
f3f9891017 |
@@ -232,7 +232,7 @@ export class AuthService {
|
|||||||
template: 'code-your-own',
|
template: 'code-your-own',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
magicLink: `${url}/magic-link?token=${generatedTokens.token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const authCookieHandler = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!redirect) {
|
if (!redirect) {
|
||||||
return res.status(HttpStatus.OK).send();
|
res.status(HttpStatus.OK).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if redirectUrl is a whitelisted url
|
// check to see if redirectUrl is a whitelisted url
|
||||||
@@ -72,7 +72,7 @@ export const authCookieHandler = (
|
|||||||
// if it is not redirect by default to REDIRECT_URL
|
// if it is not redirect by default to REDIRECT_URL
|
||||||
redirectUrl = process.env.REDIRECT_URL;
|
redirectUrl = process.env.REDIRECT_URL;
|
||||||
|
|
||||||
return res.status(HttpStatus.OK).redirect(redirectUrl);
|
res.status(HttpStatus.OK).redirect(redirectUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ declare module '@vue/runtime-core' {
|
|||||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
||||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
||||||
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
|
||||||
|
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
|
||||||
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
|
||||||
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
|
||||||
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
|
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
|
||||||
@@ -84,7 +85,6 @@ declare module '@vue/runtime-core' {
|
|||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
|
||||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||||
@@ -165,7 +165,6 @@ declare module '@vue/runtime-core' {
|
|||||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||||
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
||||||
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
|
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
|
||||||
SmartPicture: typeof import('./../../hoppscotch-ui/src/components/smart/Picture.vue')['default']
|
|
||||||
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
|
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
|
||||||
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
|
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
|
||||||
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
|
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
theme="popover"
|
theme="popover"
|
||||||
:on-shown="() => tippyActions.focus()"
|
:on-shown="() => tippyActions.focus()"
|
||||||
>
|
>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-if="currentUser.photoURL"
|
v-if="currentUser.photoURL"
|
||||||
v-tippy="{
|
v-tippy="{
|
||||||
theme: 'tooltip',
|
theme: 'tooltip',
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="
|
:title="
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions!.focus()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="`${t('environment.select')}`"
|
||||||
|
class="bg-transparent border-b border-dividerLight select-wrapper"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="selectedEnv.type !== 'NO_ENV_SELECTED'"
|
||||||
|
:label="selectedEnv.name"
|
||||||
|
class="flex-1 !justify-start pr-8 rounded-none"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-else
|
||||||
|
:label="`${t('environment.select')}`"
|
||||||
|
class="flex-1 !justify-start pr-8 rounded-none"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
role="menu"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
:label="`${t('environment.no_environment')}`"
|
||||||
|
:info-icon="
|
||||||
|
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||||
|
? IconCheck
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
class="my-2"
|
||||||
|
:active-info-icon="
|
||||||
|
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
setSelectedEnvironmentIndex({ type: 'NO_ENV_SELECTED' })
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<HoppSmartTabs
|
||||||
|
v-model="selectedEnvTab"
|
||||||
|
styles="sticky overflow-x-auto flex-shrink-0 bg-primary z-10 top-0"
|
||||||
|
render-inactive-tabs
|
||||||
|
>
|
||||||
|
<HoppSmartTab
|
||||||
|
:id="'my-environments'"
|
||||||
|
:label="`${t('environment.my_environments')}`"
|
||||||
|
>
|
||||||
|
<hr v-if="myEnvironments.length > 0" />
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(gen, index) in myEnvironments"
|
||||||
|
:key="`gen-${index}`"
|
||||||
|
:label="gen.name"
|
||||||
|
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
||||||
|
:active-info-icon="index === selectedEnv.index"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
setSelectedEnvironmentIndex({
|
||||||
|
type: 'MY_ENV',
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</HoppSmartTab>
|
||||||
|
<HoppSmartTab
|
||||||
|
:id="'team-environments'"
|
||||||
|
:label="`${t('environment.team_environments')}`"
|
||||||
|
:disabled="
|
||||||
|
!isTeamSelected ||
|
||||||
|
teamEnvLoading ||
|
||||||
|
teamEnvironmentList.length === 0 ||
|
||||||
|
environmentType === 'my-environments'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="teamEnvLoading"
|
||||||
|
class="flex flex-col items-center justify-center p-4"
|
||||||
|
>
|
||||||
|
<HoppSmartSpinner class="my-4" />
|
||||||
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
|
</div>
|
||||||
|
<hr v-if="teamEnvironmentList.length > 0" />
|
||||||
|
<div v-if="isTeamSelected" class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(gen, index) in teamEnvironmentList"
|
||||||
|
:key="`gen-team-${index}`"
|
||||||
|
:label="gen.environment.name"
|
||||||
|
:info-icon="
|
||||||
|
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
||||||
|
"
|
||||||
|
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
setSelectedEnvironmentIndex({
|
||||||
|
type: 'TEAM_ENV',
|
||||||
|
teamEnvID: gen.id,
|
||||||
|
teamID: gen.teamID,
|
||||||
|
environment: gen.environment,
|
||||||
|
})
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HoppSmartTab>
|
||||||
|
</HoppSmartTabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
import { useStream } from "~/composables/stream"
|
||||||
|
import {
|
||||||
|
selectedEnvironmentIndex$,
|
||||||
|
setSelectedEnvironmentIndex,
|
||||||
|
} from "~/newstore/environments"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
type EnvironmentType = "my-environments" | "team-environments"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
environmentType: EnvironmentType
|
||||||
|
myEnvironments: Environment[]
|
||||||
|
teamEnvironmentList: TeamEnvironment[]
|
||||||
|
teamEnvLoading: boolean
|
||||||
|
isAdapterError: boolean
|
||||||
|
errorMessage: GQLError<string>
|
||||||
|
isTeamSelected: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selectedEnvTab = ref<EnvironmentType>("my-environments")
|
||||||
|
|
||||||
|
const selectedEnvironmentIndex = useStream(
|
||||||
|
selectedEnvironmentIndex$,
|
||||||
|
{ type: "NO_ENV_SELECTED" },
|
||||||
|
setSelectedEnvironmentIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.environmentType,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal === "my-environments") {
|
||||||
|
selectedEnvTab.value = "my-environments"
|
||||||
|
} else {
|
||||||
|
selectedEnvTab.value = "team-environments"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const selectedEnv = computed(() => {
|
||||||
|
console.log("selectedEnvironmentIndex", selectedEnvironmentIndex.value)
|
||||||
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
|
return {
|
||||||
|
type: "MY_ENV",
|
||||||
|
index: selectedEnvironmentIndex.value.index,
|
||||||
|
name: props.myEnvironments[selectedEnvironmentIndex.value.index].name,
|
||||||
|
}
|
||||||
|
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||||
|
const teamEnv = props.teamEnvironmentList.find(
|
||||||
|
(env) =>
|
||||||
|
env.id ===
|
||||||
|
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.teamEnvID)
|
||||||
|
)
|
||||||
|
if (teamEnv) {
|
||||||
|
return {
|
||||||
|
type: "TEAM_ENV",
|
||||||
|
name: teamEnv.environment.name,
|
||||||
|
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { type: "NO_ENV_SELECTED" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { type: "NO_ENV_SELECTED" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Template refs
|
||||||
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
</script>
|
||||||
@@ -4,153 +4,15 @@
|
|||||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||||
>
|
>
|
||||||
<WorkspaceCurrent :section="t('tab.environments')" />
|
<WorkspaceCurrent :section="t('tab.environments')" />
|
||||||
<tippy
|
<EnvironmentsSelector
|
||||||
v-if="environmentType.type === 'my-environments'"
|
:environment-type="environmentType.type"
|
||||||
interactive
|
:my-environments="myEnvironments"
|
||||||
trigger="click"
|
:team-env-loading="loading"
|
||||||
theme="popover"
|
:team-environment-list="teamEnvironmentList"
|
||||||
:on-shown="() => tippyActions!.focus()"
|
:is-adapter-error="adapterError !== null"
|
||||||
>
|
:error-message="adapterError ? getErrorMessage(adapterError) : ''"
|
||||||
<span
|
:is-team-selected="environmentType.selectedTeam !== undefined"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
/>
|
||||||
:title="`${t('environment.select')}`"
|
|
||||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="
|
|
||||||
selectedEnv.type === 'MY_ENV' && selectedEnv.index !== undefined
|
|
||||||
"
|
|
||||||
:label="myEnvironments[selectedEnv.index].name"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-else
|
|
||||||
:label="`${t('environment.select')}`"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
role="menu"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
:label="`${t('environment.no_environment')}`"
|
|
||||||
:info-icon="
|
|
||||||
selectedEnvironmentIndex.type !== 'MY_ENV'
|
|
||||||
? IconCheck
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="selectedEnvironmentIndex.type !== 'MY_ENV'"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<hr v-if="myEnvironments.length > 0" />
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="(gen, index) in myEnvironments"
|
|
||||||
:key="`gen-${index}`"
|
|
||||||
:label="gen.name"
|
|
||||||
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
|
||||||
:active-info-icon="index === selectedEnv.index"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
<tippy v-else interactive trigger="click" theme="popover">
|
|
||||||
<span
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${t('environment.select')}`"
|
|
||||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="selectedEnv.name"
|
|
||||||
:label="selectedEnv.name"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-else
|
|
||||||
:label="`${t('environment.select')}`"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
class="flex flex-col"
|
|
||||||
role="menu"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
:label="`${t('environment.no_environment')}`"
|
|
||||||
:info-icon="
|
|
||||||
selectedEnvironmentIndex.type !== 'TEAM_ENV'
|
|
||||||
? IconCheck
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="selectedEnvironmentIndex.type !== 'TEAM_ENV'"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="loading"
|
|
||||||
class="flex flex-col items-center justify-center p-4"
|
|
||||||
>
|
|
||||||
<HoppSmartSpinner class="my-4" />
|
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
|
||||||
</div>
|
|
||||||
<hr v-if="teamEnvironmentList.length > 0" />
|
|
||||||
<div
|
|
||||||
v-if="environmentType.selectedTeam !== undefined"
|
|
||||||
class="flex flex-col"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="(gen, index) in teamEnvironmentList"
|
|
||||||
:key="`gen-team-${index}`"
|
|
||||||
:label="gen.environment.name"
|
|
||||||
:info-icon="
|
|
||||||
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
selectedEnvironmentIndex = {
|
|
||||||
type: 'TEAM_ENV',
|
|
||||||
teamEnvID: gen.id,
|
|
||||||
teamID: gen.teamID,
|
|
||||||
environment: gen.environment,
|
|
||||||
}
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="!loading && adapterError"
|
|
||||||
class="flex flex-col items-center py-4"
|
|
||||||
>
|
|
||||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
|
||||||
{{ getErrorMessage(adapterError) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
<EnvironmentsMyEnvironment
|
<EnvironmentsMyEnvironment
|
||||||
environment-index="Global"
|
environment-index="Global"
|
||||||
:environment="globalEnvironment"
|
:environment="globalEnvironment"
|
||||||
@@ -191,8 +53,6 @@ import {
|
|||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import IconCheck from "~icons/lucide/check"
|
|
||||||
import { TippyComponent } from "vue-tippy"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { workspaceStatus$ } from "~/newstore/workspace"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
@@ -249,7 +109,9 @@ watch(
|
|||||||
teamListFetched.value = true
|
teamListFetched.value = true
|
||||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||||
if (team) updateSelectedTeam(team)
|
if (team) {
|
||||||
|
updateSelectedTeam(team)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,10 +157,21 @@ watch(
|
|||||||
(teamID) => {
|
(teamID) => {
|
||||||
if (!teamID) {
|
if (!teamID) {
|
||||||
switchToMyEnvironments()
|
switchToMyEnvironments()
|
||||||
|
|
||||||
|
// If the user selected a team environment, and then switch to personal workspace,
|
||||||
|
// we need to reset the selected environment
|
||||||
|
if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||||
|
setSelectedEnvironmentIndex({
|
||||||
|
type: "NO_ENV_SELECTED",
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (teamID) {
|
} else if (teamID) {
|
||||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||||
if (team) {
|
if (team) {
|
||||||
updateSelectedTeam(team)
|
updateSelectedTeam(team)
|
||||||
|
setSelectedEnvironmentIndex({
|
||||||
|
type: "NO_ENV_SELECTED",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,33 +261,6 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
|
||||||
return {
|
|
||||||
type: "MY_ENV",
|
|
||||||
index: selectedEnvironmentIndex.value.index,
|
|
||||||
}
|
|
||||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
|
||||||
const teamEnv = teamEnvironmentList.value.find(
|
|
||||||
(env) =>
|
|
||||||
env.id ===
|
|
||||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.teamEnvID)
|
|
||||||
)
|
|
||||||
if (teamEnv) {
|
|
||||||
return {
|
|
||||||
type: "TEAM_ENV",
|
|
||||||
name: teamEnv.environment.name,
|
|
||||||
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { type: "NO_ENV_SELECTED" }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { type: "NO_ENV_SELECTED" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
if (err.type === "network_error") {
|
if (err.type === "network_error") {
|
||||||
return t("error.network_error")
|
return t("error.network_error")
|
||||||
@@ -427,7 +273,4 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template refs
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
tabindex="0"
|
||||||
|
class="relative flex items-center justify-center overflow-visible cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primaryDark"
|
||||||
|
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="url"
|
||||||
|
class="absolute object-cover object-center transition bg-primaryDark"
|
||||||
|
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||||
|
:src="url"
|
||||||
|
:alt="alt"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="absolute flex items-center justify-center object-cover object-center transition bg-primaryDark text-accentContrast"
|
||||||
|
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||||
|
:style="`background-color: ${initial ? toHex(initial) : '#480000'}`"
|
||||||
|
>
|
||||||
|
<template v-if="initial && initial.charAt(0).toUpperCase()">
|
||||||
|
{{ initial.charAt(0).toUpperCase() }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<icon-lucide-user v-else></icon-lucide-user>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="indicator"
|
||||||
|
class="border-primary border-2 h-2.5 -top-0.5 -right-0.5 w-2.5 absolute"
|
||||||
|
:class="[`rounded-${rounded}`, indicatorStyles]"
|
||||||
|
></span>
|
||||||
|
<!-- w-5 h-5 rounded-lg -->
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue"
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
alt: {
|
||||||
|
type: String,
|
||||||
|
default: "Profile picture",
|
||||||
|
},
|
||||||
|
indicator: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
indicatorStyles: {
|
||||||
|
type: String,
|
||||||
|
default: "bg-green-500",
|
||||||
|
},
|
||||||
|
rounded: {
|
||||||
|
type: String,
|
||||||
|
default: "full",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: "5",
|
||||||
|
},
|
||||||
|
initial: {
|
||||||
|
type: String as PropType<string | undefined | null>,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toHex(initial: string) {
|
||||||
|
let hash = 0
|
||||||
|
if (initial.length === 0) return hash
|
||||||
|
for (let i = 0; i < initial.length; i++) {
|
||||||
|
hash = initial.charCodeAt(i) + ((hash << 5) - hash)
|
||||||
|
hash = hash & hash
|
||||||
|
}
|
||||||
|
let color = "#"
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 255
|
||||||
|
color += `00${value.toString(16)}`.slice(-2)
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
:key="`member-${index}`"
|
:key="`member-${index}`"
|
||||||
class="inline-flex"
|
class="inline-flex"
|
||||||
>
|
>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-if="member.user.photoURL"
|
v-if="member.user.photoURL"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:url="member.user.photoURL"
|
:url="member.user.photoURL"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
class="ring-primary ring-2"
|
class="ring-primary ring-2"
|
||||||
@click="handleClick()"
|
@click="handleClick()"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="getUserName(member)"
|
:title="getUserName(member)"
|
||||||
|
|||||||
@@ -24,10 +24,7 @@
|
|||||||
>
|
>
|
||||||
<Pane class="flex flex-1 !overflow-auto">
|
<Pane class="flex flex-1 !overflow-auto">
|
||||||
<main class="flex flex-1 w-full" role="main">
|
<main class="flex flex-1 w-full" role="main">
|
||||||
<RouterView
|
<RouterView v-slot="{ Component }" class="flex flex-1">
|
||||||
v-slot="{ Component }"
|
|
||||||
class="flex flex-1 min-w-0"
|
|
||||||
>
|
|
||||||
<Transition name="fade" mode="out-in" appear>
|
<Transition name="fade" mode="out-in" appear>
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
></div>
|
></div>
|
||||||
<div class="flex flex-col justify-between px-4 space-y-8 md:flex-row">
|
<div class="flex flex-col justify-between px-4 space-y-8 md:flex-row">
|
||||||
<div class="flex items-end">
|
<div class="flex items-end">
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-if="currentUser.photoURL"
|
v-if="currentUser.photoURL"
|
||||||
:url="currentUser.photoURL"
|
:url="currentUser.photoURL"
|
||||||
:alt="
|
:alt="
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
size="16"
|
size="16"
|
||||||
rounded="lg"
|
rounded="lg"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-else
|
v-else
|
||||||
:initial="currentUser.displayName || currentUser.email"
|
:initial="currentUser.displayName || currentUser.email"
|
||||||
rounded="lg"
|
rounded="lg"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
arrow
|
arrow
|
||||||
:on-shown="() => tippyActions!.focus()"
|
:on-shown="() => tippyActions!.focus()"
|
||||||
>
|
>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-if="currentUser.photoURL"
|
v-if="currentUser.photoURL"
|
||||||
v-tippy="{
|
v-tippy="{
|
||||||
theme: 'tooltip',
|
theme: 'tooltip',
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
:alt="currentUser.displayName ?? 'No Name'"
|
:alt="currentUser.displayName ?? 'No Name'"
|
||||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPicture
|
<ProfilePicture
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
||||||
|
|||||||
@@ -33,40 +33,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts">
|
||||||
withDefaults(
|
import { defineComponent, PropType } from 'vue';
|
||||||
defineProps<{
|
|
||||||
url: string
|
|
||||||
alt: string
|
|
||||||
indicator: boolean
|
|
||||||
indicatorStyles: string
|
|
||||||
rounded: string
|
|
||||||
size: string
|
|
||||||
initial: string | undefined | null
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
url: "",
|
|
||||||
alt: "Profile picture",
|
|
||||||
indicator: false,
|
|
||||||
indicatorStyles: "bg-green-500",
|
|
||||||
rounded: "full",
|
|
||||||
size: "5",
|
|
||||||
initial: "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const toHex = (initial: string) => {
|
export default defineComponent({
|
||||||
let hash = 0
|
props: {
|
||||||
if (initial.length === 0) return hash
|
url: {
|
||||||
for (let i = 0; i < initial.length; i++) {
|
type: String,
|
||||||
hash = initial.charCodeAt(i) + ((hash << 5) - hash)
|
default: '',
|
||||||
hash = hash & hash
|
},
|
||||||
}
|
alt: {
|
||||||
let color = "#"
|
type: String,
|
||||||
for (let i = 0; i < 3; i++) {
|
default: 'Profile picture',
|
||||||
const value = (hash >> (i * 8)) & 255
|
},
|
||||||
color += `00${value.toString(16)}`.slice(-2)
|
indicator: {
|
||||||
}
|
type: Boolean,
|
||||||
return color
|
default: false,
|
||||||
}
|
},
|
||||||
|
indicatorStyles: {
|
||||||
|
type: String,
|
||||||
|
default: 'bg-green-500',
|
||||||
|
},
|
||||||
|
rounded: {
|
||||||
|
type: String,
|
||||||
|
default: 'full',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '5',
|
||||||
|
},
|
||||||
|
initial: {
|
||||||
|
type: String as PropType<string | undefined | null>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toHex(initial: string) {
|
||||||
|
let hash = 0;
|
||||||
|
if (initial.length === 0) return hash;
|
||||||
|
for (let i = 0; i < initial.length; i++) {
|
||||||
|
hash = initial.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 255;
|
||||||
|
color += `00${value.toString(16)}`.slice(-2);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -6,7 +6,7 @@ const isAdmin = () => {
|
|||||||
return user ? user.isAdmin : false;
|
return user ? user.isAdmin : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GUEST_ROUTES = ['index', 'enter'];
|
const GUEST_ROUTES = ['index', 'magic-link'];
|
||||||
|
|
||||||
const isGuestRoute = (to: unknown) => GUEST_ROUTES.includes(to as string);
|
const isGuestRoute = (to: unknown) => GUEST_ROUTES.includes(to as string);
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,3 @@ export { default as HoppSmartTabs } from "./Tabs.vue"
|
|||||||
export { default as HoppSmartToggle } from "./Toggle.vue"
|
export { default as HoppSmartToggle } from "./Toggle.vue"
|
||||||
export { default as HoppSmartWindow } from "./Window.vue"
|
export { default as HoppSmartWindow } from "./Window.vue"
|
||||||
export { default as HoppSmartWindows } from "./Windows.vue"
|
export { default as HoppSmartWindows } from "./Windows.vue"
|
||||||
export { default as HoppSmartPicture } from "./Picture.vue"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user