Skip to content
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 20 additions & 3 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@
"disabled-capgo-updates": "Disabled Capgo updates.",
"discord": "discord",
"discord-username": "Discord username",
"discord-username-help": "Your username without the at sign.",
"discord-username-help": "Without the at sign. Used to match your Capgo Cloud account on Discord support.",
"discover-module-in-a": "Discover module in Awesome-capacitor",
"discover-your-bundle": "See your bundle appear in the list",
"discover-your-dashbo": "Discover your dashboard !",
Expand Down Expand Up @@ -1295,12 +1295,12 @@
"github-username": "GitHub username",
"github-username-confirm": "Yes",
"github-username-confirm-description": "Is this the GitHub profile you want to use?",
"github-username-dialog-description": "Enter your GitHub username and we will verify the profile before saving it.",
"github-username-dialog-description": "Enter your GitHub username and we will verify the profile before saving it. We use it to match your Capgo Cloud account on GitHub issues. Priority support is included on Maker and above, and for everyone during trial.",
"github-username-error-invalid_username": "Enter a valid GitHub username.",
"github-username-error-not_found": "We could not find that GitHub profile.",
"github-username-error-rate_limited": "GitHub is temporarily rate limited. Please try again later.",
"github-username-error-request_failed": "We could not load that GitHub profile. Please try again.",
"github-username-help": "Your username without the at sign.",
"github-username-help": "Without the at sign. Used to match your Capgo Cloud account on GitHub issues. Priority support: Maker and above, and all trials.",
"github-username-placeholder": "octocat",
"github-username-select": "Select a GitHub username",
"global": "Global",
Expand Down Expand Up @@ -1709,6 +1709,7 @@
"one-number": "1 number",
"one-special-character": "1 special character",
"one-uppercase": "1 uppercase",
"optional": "Optional",
"open": "Open",
"open-channel": "Open channel",
"open-in-external": "Open in new tab",
Expand Down Expand Up @@ -2168,6 +2169,12 @@
"signing-secret-unavailable": "Signing secrets are only shown when a webhook is created.",
"size": "Size",
"size-not-found": "Not found",
"social-username-help": "Both fields are optional. You can add or change them later in Account settings.",
"support-usernames-prompt-dismiss-forever": "Don't ask again",
"support-usernames-prompt-save": "Save",
"support-usernames-prompt-remind-later": "Remind me later",
"support-usernames-prompt-helper": "Add Discord and GitHub so we can match your Capgo Cloud account when you open a support issue. Priority GitHub support is included on Maker and above, and for everyone during trial.",
"support-usernames-prompt-title": "Speed up Capgo Cloud support",
"something-went-wrong-try-again-later": "Something went wrong! Try again later.",
"sso-add-provider": "Add SSO Provider",
"sso-configuration": "SSO Configuration",
Expand Down Expand Up @@ -2557,6 +2564,16 @@
"notification-settings-save-success": "Notification settings saved",
"notification-update-push-success": "Update push queued",
"organization-onboarding-subtitle-after-app": "Your app is ready. Now create the organization that will own it.",
"organization-onboarding-support-usernames-helper": "Link your Discord and GitHub so we can match your Capgo Cloud account when you ask for help.",
"organization-onboarding-support-usernames-title": "Faster Capgo Cloud support",
"organization-onboarding-support-usernames-save-failed": "Could not save your support usernames. Check your connection and try again.",
"organization-onboarding-support-usernames-helper-new-user": "You are in Capgo onboarding, on the organization setup step (after your app details). Optionally link Discord and GitHub so we can match your Capgo Cloud account when you need support.",
"organization-onboarding-support-usernames-step": "Onboarding \u00b7 Create organization",
"organization-onboarding-support-github-verified": "Verified",
"organization-onboarding-support-github-cta": "Verify GitHub username",
"organization-onboarding-support-benefit-trial": "Included in trial",
"organization-onboarding-support-benefit-maker": "Priority on Maker+",
"organization-onboarding-support-benefit-match": "Match your account",
"organization-onboarding-mode-app-name": "Use {name}",
"organization-onboarding-mode-app-name-helper": "Keep the same name as your app. You can still edit it below.",
"organization-onboarding-finish-setup": "Finish setup",
Expand Down
2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineAsyncComponent, watch } from 'vue'

const Toast = defineAsyncComponent(() => import('~/components/Toast.vue'))
const DialogV2 = defineAsyncComponent(() => import('~/components/DialogV2.vue'))
const SupportUsernamesPrompt = defineAsyncComponent(() => import('~/components/dashboard/SupportUsernamesPrompt.vue'))

const route = useRoute()
const display = useDisplayStore()
Expand All @@ -21,5 +22,6 @@ watch(
<RouterView class="h-full overflow-hidden" />
<Toast />
<DialogV2 />
<SupportUsernamesPrompt />
</div>
</template>
4 changes: 4 additions & 0 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ declare module 'vue' {
ConnectAppPicker: typeof import('./components/connect/ConnectAppPicker.vue')['default']
CreditsCta: typeof import('./components/CreditsCta.vue')['default']
DataTable: typeof import('./components/DataTable.vue')['default']
DateRangePicker: typeof import('./components/DateRangePicker.vue')['default']
DeliveryLatencyPanel: typeof import('./components/dashboard/DeliveryLatencyPanel.vue')['default']
DemoOnboardingGate: typeof import('./components/dashboard/DemoOnboardingGate.vue')['default']
DemoOnboardingModal: typeof import('./components/dashboard/DemoOnboardingModal.vue')['default']
Expand All @@ -65,6 +66,7 @@ declare module 'vue' {
DropdownOrganization: typeof import('./components/dashboard/DropdownOrganization.vue')['default']
DropdownProfile: typeof import('./components/dashboard/DropdownProfile.vue')['default']
FailedCard: typeof import('./components/FailedCard.vue')['default']
GitHubProfileDialog: typeof import('./components/dashboard/GitHubProfileDialog.vue')['default']
GroupsRbacManager: typeof import('./components/organization/GroupsRbacManager.vue')['default']
HistoryTable: typeof import('./components/tables/HistoryTable.vue')['default']
IIonCopyOutline: typeof import('~icons/ion/copy-outline')['default']
Expand All @@ -74,6 +76,7 @@ declare module 'vue' {
LineChartStats: typeof import('./components/dashboard/LineChartStats.vue')['default']
LogTable: typeof import('./components/tables/LogTable.vue')['default']
Navbar: typeof import('./components/Navbar.vue')['default']
OnboardingSupportUsernames: typeof import('./components/dashboard/OnboardingSupportUsernames.vue')['default']
OrgEmailNotificationsPanel: typeof import('./components/dashboard/OrgEmailNotificationsPanel.vue')['default']
OrgNotificationStatsPanel: typeof import('./components/dashboard/OrgNotificationStatsPanel.vue')['default']
PageLoader: typeof import('./components/PageLoader.vue')['default']
Expand All @@ -97,6 +100,7 @@ declare module 'vue' {
StepsBuild: typeof import('./components/dashboard/StepsBuild.vue')['default']
StepsBundle: typeof import('./components/dashboard/StepsBundle.vue')['default']
StoreReleaseValidationModal: typeof import('./components/dashboard/StoreReleaseValidationModal.vue')['default']
SupportUsernamesPrompt: typeof import('./components/dashboard/SupportUsernamesPrompt.vue')['default']
TableLog: typeof import('./components/TableLog.vue')['default']
Tabs: typeof import('./components/Tabs.vue')['default']
TabSidebar: typeof import('./components/TabSidebar.vue')['default']
Expand Down
111 changes: 111 additions & 0 deletions src/components/dashboard/GitHubProfileDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { useGitHubProfileDialog } from '~/composables/useGitHubProfileDialog'

const props = withDefaults(defineProps<{
dialogId?: string
}>(), {
dialogId: 'github-profile',
})

const { t } = useI18n()

const {
dialogStore,
githubUsername,
githubUsernameInput,
githubProfile,
githubProfileLoading,
githubProfileSaving,
githubProfileError,
openGitHubProfileDialog,
closeGitHubProfileDialog,
findGitHubProfile,
confirmGitHubProfile,
clearGitHubProfile,
} = useGitHubProfileDialog(props.dialogId)

defineExpose({
githubUsername,
openGitHubProfileDialog,
})
</script>

<template>
<Teleport v-if="dialogStore.showDialog && dialogStore.dialogOptions?.id === dialogId" to="#dialog-v2-content" defer>
<div>
<template v-if="!githubProfile">
<label :for="`${dialogId}-input`" class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('github-username') }}
</label>
<input
:id="`${dialogId}-input`"
v-model="githubUsernameInput"
type="text"
autocomplete="off"
maxlength="39"
class="d-input w-full"
:disabled="githubProfileLoading || githubProfileSaving"
:placeholder="t('github-username-placeholder')"
@keydown.enter.prevent="findGitHubProfile"
>
</template>

<div v-else class="flex items-center gap-4 rounded-lg border border-slate-200 p-4 dark:border-slate-700">
<img :src="githubProfile.avatarUrl" :alt="githubProfile.login" class="h-16 w-16 rounded-full" width="64" height="64">
<div class="min-w-0">
<p class="truncate font-semibold text-gray-900 dark:text-white">
{{ githubProfile.name || githubProfile.login }}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">
@{{ githubProfile.login }}
</p>
</div>
</div>

<p v-if="githubProfile" class="mt-4 text-sm text-gray-600 dark:text-gray-300">
{{ t('github-username-confirm-description') }}
</p>
<p v-if="githubProfileError" class="mt-3 text-sm text-red-600 dark:text-red-400" role="alert">
{{ githubProfileError }}
</p>

<div class="mt-6 flex justify-end gap-3">
<button type="button" class="d-btn d-btn-ghost" :disabled="githubProfileLoading || githubProfileSaving" @click="closeGitHubProfileDialog">
{{ t('button-cancel') }}
</button>
<button
v-if="!githubProfile && githubUsername"
type="button"
class="d-btn d-btn-error"
:aria-label="t('button-remove')"
:disabled="githubProfileLoading || githubProfileSaving"
@click="clearGitHubProfile"
>
<Spinner v-if="githubProfileSaving" size="w-4 h-4" />
<span v-else>{{ t('button-remove') }}</span>
</button>
<button
v-if="githubProfile"
type="button"
class="d-btn d-btn-primary"
:disabled="githubProfileSaving"
@click="confirmGitHubProfile"
>
<Spinner v-if="githubProfileSaving" size="w-4 h-4" />
<span v-else>{{ t('github-username-confirm') }}</span>
</button>
<button
v-else
type="button"
class="d-btn d-btn-primary"
:disabled="githubProfileLoading || !githubUsernameInput.trim()"
@click="findGitHubProfile"
>
<Spinner v-if="githubProfileLoading" size="w-4 h-4" />
<span v-else>{{ t('next') }}</span>
</button>
</div>
</div>
</Teleport>
</template>
164 changes: 164 additions & 0 deletions src/components/dashboard/OnboardingSupportUsernames.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<script setup lang="ts">
import { computed, unref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import IconDiscord from '~icons/ic/round-discord'
import IconBadgeCheck from '~icons/lucide/badge-check'
import IconChevronRight from '~icons/lucide/chevron-right'
import IconGithub from '~icons/lucide/github'
import IconHeadset from '~icons/lucide/headset'
import IconSparkles from '~icons/lucide/sparkles'
import IconZap from '~icons/lucide/zap'
import GitHubProfileDialog from '~/components/dashboard/GitHubProfileDialog.vue'

const props = withDefaults(defineProps<{
dialogId?: string
/** New-user Capgo onboarding (app → organization), not additional-org create */
isNewUserOnboarding?: boolean
compact?: boolean
hideIntro?: boolean
}>(), {
dialogId: 'onboarding-github-profile',
isNewUserOnboarding: false,
compact: false,
hideIntro: false,
})

const discordUsername = defineModel<string>('discordUsername', { required: true })

const { t } = useI18n()
const githubDialog = useTemplateRef('githubDialog')

Check failure on line 29 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'githubDialog' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

Check failure on line 29 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'githubDialog' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

const githubUsername = computed(() => unref(githubDialog.value?.githubUsername) ?? '')

Check failure on line 31 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

Check failure on line 31 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'githubUsername' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

Check failure on line 31 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

Check failure on line 31 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'githubUsername' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

const supportBenefits = computed(() => [
{ icon: IconBadgeCheck, label: t('organization-onboarding-support-benefit-match') },
{ icon: IconZap, label: t('organization-onboarding-support-benefit-maker') },
{ icon: IconSparkles, label: t('organization-onboarding-support-benefit-trial') },
])

const helperText = computed(() => {
if (props.isNewUserOnboarding)
return t('organization-onboarding-support-usernames-helper-new-user')
return t('organization-onboarding-support-usernames-helper')
})

function openGitHubProfileDialog() {
githubDialog.value?.openGitHubProfileDialog()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</script>

<template>
<section
class="overflow-hidden rounded-2xl border border-primary-500/20 bg-linear-to-br from-primary-500/5 via-white to-slate-50 shadow-sm dark:border-primary-500/30 dark:from-primary-500/10 dark:via-slate-950/90 dark:to-slate-950"
:class="{ 'shadow-none': compact }"
aria-labelledby="onboarding-support-usernames-title"
>
<div class="space-y-4 p-4 sm:p-5">
<div v-if="!hideIntro" class="flex items-start gap-3">
<span class="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-primary-500 text-white shadow-sm shadow-primary-500/30">
<IconHeadset class="h-5 w-5" aria-hidden="true" />
</span>
<div class="min-w-0">
<p
v-if="isNewUserOnboarding"
class="text-xs font-medium uppercase tracking-wide text-primary-600 dark:text-primary-300"
>
{{ t('organization-onboarding-support-usernames-step') }}
</p>
<h3 id="onboarding-support-usernames-title" class="text-base font-semibold text-slate-950 dark:text-white" :class="{ 'mt-1': isNewUserOnboarding }">
{{ t('organization-onboarding-support-usernames-title') }}
</h3>
<p class="mt-1 text-sm leading-6 text-slate-600 dark:text-slate-300">
{{ helperText }}
</p>
</div>
</div>
<h3 v-else id="onboarding-support-usernames-title" class="sr-only">

Check failure on line 76 in src/components/dashboard/OnboardingSupportUsernames.vue

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Duplicate id "onboarding-support-usernames-title" found. First occurrence was on line 68.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo&issues=AZ_Hl1qU0ZTxtRXosets&open=AZ_Hl1qU0ZTxtRXosets&pullRequest=2832
{{ t('organization-onboarding-support-usernames-title') }}
</h3>

<ul class="flex flex-wrap gap-2" :aria-label="t('organization-onboarding-support-usernames-title')">
<li
v-for="benefit in supportBenefits"
:key="benefit.label"
class="inline-flex min-h-9 items-center gap-1.5 rounded-full border border-slate-200 bg-white/90 px-3 text-xs font-medium text-slate-700 dark:border-white/15 dark:bg-slate-900/80 dark:text-slate-200"
>
<component :is="benefit.icon" class="h-3.5 w-3.5 text-primary-500" aria-hidden="true" />
<span>{{ benefit.label }}</span>
</li>
</ul>

<div class="grid gap-3 sm:grid-cols-2">
<div class="rounded-xl border border-slate-200 bg-white p-3 dark:border-white/15 dark:bg-slate-950/80">
<label for="onboarding-discord-username" class="flex items-center gap-2 text-sm font-medium text-slate-800 dark:text-slate-200">
<IconDiscord class="h-4 w-4 text-primary-500" aria-hidden="true" />
<span>{{ t('discord-username') }}</span>
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-normal text-slate-500 dark:bg-slate-800 dark:text-slate-400">
{{ t('optional') }}
</span>
</label>
<div class="relative mt-2">
<span class="pointer-events-none absolute inset-y-0 left-3 flex items-center text-slate-400" aria-hidden="true">
@
</span>
<input
id="onboarding-discord-username"
v-model="discordUsername"
type="text"
autocomplete="off"
maxlength="32"
:placeholder="t('discord-username')"
aria-describedby="onboarding-discord-username-help"
data-test="onboarding-discord-username"
class="min-h-11 w-full rounded-xl border border-slate-300 bg-white py-2 pr-3 pl-8 text-base text-slate-950 outline-none transition placeholder:text-slate-400 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/15 sm:text-sm dark:border-white/20 dark:bg-slate-950 dark:text-white dark:placeholder:text-slate-500 dark:focus:border-primary-500 dark:focus:ring-primary-500/30"
>
</div>
<p id="onboarding-discord-username-help" class="mt-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
{{ t('discord-username-help') }}
</p>
</div>

<div class="rounded-xl border border-slate-200 bg-white p-3 dark:border-white/15 dark:bg-slate-950/80">
<label for="onboarding-github-username" class="flex items-center gap-2 text-sm font-medium text-slate-800 dark:text-slate-200">
<IconGithub class="h-4 w-4 text-primary-500" aria-hidden="true" />
<span>{{ t('github-username') }}</span>
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-normal text-slate-500 dark:bg-slate-800 dark:text-slate-400">
{{ t('optional') }}
</span>
</label>
<button
id="onboarding-github-username"
type="button"
class="mt-2 flex min-h-11 w-full cursor-pointer items-center gap-2 rounded-xl border border-slate-300 bg-white px-3 text-left text-base text-slate-950 outline-none transition hover:border-primary-500/50 hover:bg-primary-500/5 focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/15 sm:text-sm dark:border-white/20 dark:bg-slate-950 dark:text-white dark:hover:border-primary-500/50 dark:hover:bg-primary-500/10 dark:focus-visible:border-primary-500 dark:focus-visible:ring-primary-500/30"
aria-describedby="onboarding-github-username-help"
data-test="onboarding-github-username"
@click="openGitHubProfileDialog"
>
<span class="flex min-w-0 flex-1 items-center gap-2">
<template v-if="githubUsername">
<IconBadgeCheck class="h-4 w-4 shrink-0 text-emerald-500" aria-hidden="true" />
<span class="truncate font-medium">{{ githubUsername }}</span>
<span class="rounded-full bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-700 dark:text-emerald-300">
{{ t('organization-onboarding-support-github-verified') }}
</span>
</template>
<span v-else class="truncate text-slate-500 dark:text-slate-400">
{{ t('organization-onboarding-support-github-cta') }}
</span>
</span>
<IconChevronRight class="h-4 w-4 shrink-0 text-slate-400" aria-hidden="true" />
</button>
<p id="onboarding-github-username-help" class="mt-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
{{ t('github-username-help') }}
</p>
</div>
</div>

<p id="onboarding-support-usernames-help" class="text-xs leading-5 text-slate-500 dark:text-slate-400">
{{ t('social-username-help') }}
</p>
</div>
</section>

<GitHubProfileDialog ref="githubDialog" :dialog-id="dialogId" />

Check failure on line 163 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'__VLS_32' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

Check failure on line 163 in src/components/dashboard/OnboardingSupportUsernames.vue

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'__VLS_32' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
</template>
Loading
Loading