feat(fe): Tutorials page (#4120)
* Tutorials Page * Add tutorials page * Update Page.vue * Changes from PR * Updates from call * Remove page added in error * Update Page.vue * Remove shallowref * Update mixpanel name
This commit is contained in:
committed by
GitHub
parent
b1ed49297b
commit
45d7d4d02b
@@ -33,11 +33,12 @@
|
||||
name="categories"
|
||||
label="Categories"
|
||||
placeholder="All categories"
|
||||
class="md:min-w-80"
|
||||
class="md:w-80"
|
||||
allow-unset
|
||||
:items="categories"
|
||||
size="base"
|
||||
color="foundation"
|
||||
clearable
|
||||
>
|
||||
<template #something-selected="{ value }">
|
||||
{{ isArray(value) ? value[0].name : value.name }}
|
||||
|
||||
@@ -56,23 +56,16 @@
|
||||
<section>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-heading-sm text-foreground-2">Tutorials</h2>
|
||||
<FormButton
|
||||
color="outline"
|
||||
size="sm"
|
||||
to="https://www.speckle.systems/tutorials"
|
||||
external
|
||||
target="_blank"
|
||||
>
|
||||
View all
|
||||
<FormButton color="outline" size="sm" :to="tutorialsRoute">
|
||||
View more
|
||||
</FormButton>
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 pt-5"
|
||||
>
|
||||
<DashboardTutorialCard
|
||||
v-for="tutorialItem in tutorialItems"
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-5">
|
||||
<TutorialsCard
|
||||
v-for="tutorialItem in tutorialItems.slice(0, 4)"
|
||||
:key="tutorialItem.title"
|
||||
:tutorial-item="tutorialItem"
|
||||
source="dashboard"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -93,14 +86,15 @@ import {
|
||||
docsPageUrl,
|
||||
forumPageUrl,
|
||||
homeRoute,
|
||||
projectsRoute
|
||||
projectsRoute,
|
||||
tutorialsRoute
|
||||
} from '~~/lib/common/helpers/route'
|
||||
import type { ManagerExtension } from '~~/lib/common/utils/downloadManager'
|
||||
import { downloadManager } from '~~/lib/common/utils/downloadManager'
|
||||
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { PromoBanner } from '~/lib/promo-banners/types'
|
||||
import { tutorials } from '~/lib/dashboard/helpers/tutorials'
|
||||
import { tutorialItems } from '~/lib/dashboard/helpers/tutorials'
|
||||
import { useUserProjectsUpdatedTracking } from '~~/lib/user/composables/projectUpdates'
|
||||
|
||||
const mixpanel = useMixpanel()
|
||||
@@ -120,7 +114,6 @@ useUserProjectsUpdatedTracking()
|
||||
|
||||
const promoBanners = ref<PromoBanner[]>()
|
||||
const openNewProject = ref(false)
|
||||
const tutorialItems = shallowRef(tutorials)
|
||||
const quickStartItems = shallowRef<QuickStartItem[]>([
|
||||
{
|
||||
title: 'Install Speckle manager',
|
||||
|
||||
@@ -63,6 +63,17 @@
|
||||
</template>
|
||||
</LayoutSidebarMenuGroupItem>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink :to="tutorialsRoute" @click="isOpenMobile = false">
|
||||
<LayoutSidebarMenuGroupItem
|
||||
label="Tutorials"
|
||||
:active="isActive(tutorialsRoute)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconTutorials class="size-4 ml-px text-foreground-2" />
|
||||
</template>
|
||||
</LayoutSidebarMenuGroupItem>
|
||||
</NuxtLink>
|
||||
</LayoutSidebarMenuGroup>
|
||||
|
||||
<LayoutSidebarMenuGroup
|
||||
@@ -178,7 +189,8 @@ import {
|
||||
workspaceRoute,
|
||||
workspacesRoute,
|
||||
workspaceCreateRoute,
|
||||
connectorsRoute
|
||||
connectorsRoute,
|
||||
tutorialsRoute
|
||||
} from '~/lib/common/helpers/route'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="16"
|
||||
viewBox="0 0 18 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.5 13.8154C2.64014 13.1571 3.93347 12.8105 5.25 12.8105C6.56652 12.8105 7.85986 13.1571 9 13.8154C10.1401 13.1571 11.4335 12.8105 12.75 12.8105C14.0665 12.8105 15.3599 13.1571 16.5 13.8154"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M1.5 2.98137C2.64014 2.32311 3.93347 1.97656 5.25 1.97656C6.56652 1.97656 7.85986 2.32311 9 2.98137C10.1401 2.32311 11.4335 1.97656 12.75 1.97656C14.0665 1.97656 15.3599 2.32311 16.5 2.98137"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M1.5 2.98242V13.8158"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M9 2.98242V13.8158"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 2.98242V13.8158"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
+4
-2
@@ -6,7 +6,7 @@
|
||||
<NuxtImg
|
||||
:src="tutorialItem.image"
|
||||
:alt="tutorialItem.title"
|
||||
class="h-32 w-full object-cover"
|
||||
class="aspect-video w-full object-cover"
|
||||
width="400"
|
||||
height="225"
|
||||
/>
|
||||
@@ -27,12 +27,14 @@ const mixpanel = useMixpanel()
|
||||
|
||||
const props = defineProps<{
|
||||
tutorialItem: TutorialItem
|
||||
source: 'tutorials' | 'dashboard'
|
||||
}>()
|
||||
|
||||
const trackClick = () => {
|
||||
mixpanel.track('Tutorial clicked', {
|
||||
title: props.tutorialItem.title,
|
||||
url: props.tutorialItem.url
|
||||
url: props.tutorialItem.url,
|
||||
source: props.source
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<section class="flex items-center gap-2">
|
||||
<div class="flex flex-col gap-2 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<IconTutorials class="size-4" />
|
||||
<h1 class="text-heading-lg">Tutorials</h1>
|
||||
</div>
|
||||
<p class="text-body-sm text-foreground-2">
|
||||
Get started with Speckle with step-by-step instructions for all skill
|
||||
levels.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="flex gap-4 flex-col">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<TutorialsCard
|
||||
v-for="tutorial in tutorialItems"
|
||||
:key="tutorial.title"
|
||||
:tutorial-item="tutorial"
|
||||
source="tutorials"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center mt-4">
|
||||
<FormButton
|
||||
label="View all tutorials"
|
||||
to="https://www.speckle.systems/tutorials"
|
||||
target="_blank"
|
||||
color="outline"
|
||||
external
|
||||
@click="trackViewAllClick"
|
||||
>
|
||||
View all tutorials
|
||||
</FormButton>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { tutorialItems } from '~/lib/dashboard/helpers/tutorials'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
|
||||
const mixpanel = useMixpanel()
|
||||
|
||||
const trackViewAllClick = () => {
|
||||
mixpanel.track('View All Tutorials Button Clicked')
|
||||
}
|
||||
</script>
|
||||
@@ -16,6 +16,7 @@ export const verifyEmailRoute = '/verify-email'
|
||||
export const verifyEmailCountdownRoute = '/verify-email?source=registration'
|
||||
export const serverManagementRoute = '/server-management'
|
||||
export const connectorsRoute = '/connectors'
|
||||
export const tutorialsRoute = '/tutorials'
|
||||
export const downloadManagerUrl = 'https://speckle.systems/download'
|
||||
export const docsPageUrl = 'https://speckle.guide/'
|
||||
export const forumPageUrl = 'https://speckle.community/'
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { TutorialItem } from '~/lib/dashboard/helpers/types'
|
||||
|
||||
export const tutorials: TutorialItem[] = [
|
||||
export const tutorialItems: TutorialItem[] = [
|
||||
{
|
||||
title: 'Get Civil 3D Pipe Networks Into Revit as Families',
|
||||
image:
|
||||
'https://cdn.prod.website-files.com/66c31b5a50432200dc753cc4/67b8980920e42b89aff75a3f_C3d%20to%20Revit.jpg',
|
||||
url: 'https://www.speckle.systems/tutorials/pipe-networks-civil3d-revit'
|
||||
},
|
||||
{
|
||||
title: 'How To Get Data From Grasshopper Into Power BI',
|
||||
image:
|
||||
@@ -42,11 +48,5 @@ export const tutorials: TutorialItem[] = [
|
||||
image:
|
||||
'https://cdn.prod.website-files.com/66c31b5a50432200dc753cc4/66f9c50ac50a28b58fe0e10b_66e047f505114bd4a6854b6a_216-blocks-to-families%25400.5x.png',
|
||||
url: 'https://www.speckle.systems/tutorials/new-in-2-16-block-to-family-conversion'
|
||||
},
|
||||
{
|
||||
title: 'SketchUp Connector for Mac',
|
||||
image:
|
||||
'https://cdn.prod.website-files.com/66c31b5a50432200dc753cc4/66f9c508c50a28b58fe0ddef_66e047f6aacfa88a6524a956_final-blog.jpeg',
|
||||
url: 'https://www.speckle.systems/tutorials/sketchup-connector-for-mac'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<TutorialsPage />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
useHead({
|
||||
title: 'Tutorials'
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth']
|
||||
})
|
||||
</script>
|
||||
@@ -2036,7 +2036,7 @@ export type Project = {
|
||||
versions: VersionCollection;
|
||||
/** Return metadata about resources being requested in the viewer */
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
visibility: SimpleProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3147,6 +3147,13 @@ export type SetPrimaryUserEmailInput = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** Visbility without the "discoverable" option */
|
||||
export const SimpleProjectVisibility = {
|
||||
Private: 'PRIVATE',
|
||||
Unlisted: 'UNLISTED'
|
||||
} as const;
|
||||
|
||||
export type SimpleProjectVisibility = typeof SimpleProjectVisibility[keyof typeof SimpleProjectVisibility];
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -5160,6 +5167,7 @@ export type ResolversTypes = {
|
||||
ServerWorkspacesInfo: ResolverTypeWrapper<GraphQLEmptyReturn>;
|
||||
SessionPaymentStatus: SessionPaymentStatus;
|
||||
SetPrimaryUserEmailInput: SetPrimaryUserEmailInput;
|
||||
SimpleProjectVisibility: SimpleProjectVisibility;
|
||||
SmartTextEditorValue: ResolverTypeWrapper<SmartTextEditorValueGraphQLReturn>;
|
||||
SortDirection: SortDirection;
|
||||
Stream: ResolverTypeWrapper<StreamGraphQLReturn>;
|
||||
@@ -6328,7 +6336,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
version?: Resolver<ResolversTypes['Version'], ParentType, ContextType, RequireFields<ProjectVersionArgs, 'id'>>;
|
||||
versions?: Resolver<ResolversTypes['VersionCollection'], ParentType, ContextType, RequireFields<ProjectVersionsArgs, 'limit'>>;
|
||||
viewerResources?: Resolver<Array<ResolversTypes['ViewerResourceGroup']>, ParentType, ContextType, RequireFields<ProjectViewerResourcesArgs, 'loadedVersionsOnly' | 'resourceIdString'>>;
|
||||
visibility?: Resolver<ResolversTypes['ProjectVisibility'], ParentType, ContextType>;
|
||||
visibility?: Resolver<ResolversTypes['SimpleProjectVisibility'], ParentType, ContextType>;
|
||||
webhooks?: Resolver<ResolversTypes['WebhookCollection'], ParentType, ContextType, Partial<ProjectWebhooksArgs>>;
|
||||
workspace?: Resolver<Maybe<ResolversTypes['Workspace']>, ParentType, ContextType>;
|
||||
workspaceId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
|
||||
@@ -2016,7 +2016,7 @@ export type Project = {
|
||||
versions: VersionCollection;
|
||||
/** Return metadata about resources being requested in the viewer */
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
visibility: SimpleProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3127,6 +3127,13 @@ export type SetPrimaryUserEmailInput = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** Visbility without the "discoverable" option */
|
||||
export const SimpleProjectVisibility = {
|
||||
Private: 'PRIVATE',
|
||||
Unlisted: 'UNLISTED'
|
||||
} as const;
|
||||
|
||||
export type SimpleProjectVisibility = typeof SimpleProjectVisibility[keyof typeof SimpleProjectVisibility];
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -4914,7 +4921,7 @@ export type CrossSyncProjectMetadataQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CrossSyncProjectMetadataQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, versions: { __typename?: 'VersionCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Version', id: string, createdAt: string, model: { __typename?: 'Model', id: string, name: string } }> } } };
|
||||
export type CrossSyncProjectMetadataQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, versions: { __typename?: 'VersionCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Version', id: string, createdAt: string, model: { __typename?: 'Model', id: string, name: string } }> } } };
|
||||
|
||||
export type CrossSyncClientTestQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
@@ -2017,7 +2017,7 @@ export type Project = {
|
||||
versions: VersionCollection;
|
||||
/** Return metadata about resources being requested in the viewer */
|
||||
viewerResources: Array<ViewerResourceGroup>;
|
||||
visibility: ProjectVisibility;
|
||||
visibility: SimpleProjectVisibility;
|
||||
webhooks: WebhookCollection;
|
||||
workspace?: Maybe<Workspace>;
|
||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||
@@ -3128,6 +3128,13 @@ export type SetPrimaryUserEmailInput = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
/** Visbility without the "discoverable" option */
|
||||
export const SimpleProjectVisibility = {
|
||||
Private: 'PRIVATE',
|
||||
Unlisted: 'UNLISTED'
|
||||
} as const;
|
||||
|
||||
export type SimpleProjectVisibility = typeof SimpleProjectVisibility[keyof typeof SimpleProjectVisibility];
|
||||
export type SmartTextEditorValue = {
|
||||
__typename?: 'SmartTextEditorValue';
|
||||
/** File attachments, if any */
|
||||
@@ -5152,7 +5159,7 @@ export type UpdateWorkspaceProjectRoleMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWorkspaceProjectRoleMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', projects: { __typename?: 'WorkspaceProjectMutations', updateRole: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } } };
|
||||
export type UpdateWorkspaceProjectRoleMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', projects: { __typename?: 'WorkspaceProjectMutations', updateRole: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } } };
|
||||
|
||||
export type BasicStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, requesterId: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } };
|
||||
|
||||
@@ -5495,7 +5502,7 @@ export type EditProjectCommentMutationVariables = Exact<{
|
||||
|
||||
export type EditProjectCommentMutation = { __typename?: 'Mutation', commentMutations: { __typename?: 'CommentMutations', edit: { __typename?: 'Comment', id: string, rawText: string, authorId: string, text: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null } } } };
|
||||
|
||||
export type BasicProjectFieldsFragment = { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string };
|
||||
export type BasicProjectFieldsFragment = { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string };
|
||||
|
||||
export type AdminProjectListQueryVariables = Exact<{
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -5506,7 +5513,7 @@ export type AdminProjectListQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type AdminProjectListQuery = { __typename?: 'Query', admin: { __typename?: 'AdminQueries', projectList: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string }> } } };
|
||||
export type AdminProjectListQuery = { __typename?: 'Query', admin: { __typename?: 'AdminQueries', projectList: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string }> } } };
|
||||
|
||||
export type GetProjectObjectQueryVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
@@ -5528,7 +5535,7 @@ export type CreateProjectMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', create: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } };
|
||||
export type CreateProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', create: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } };
|
||||
|
||||
export type BatchDeleteProjectsMutationVariables = Exact<{
|
||||
ids: Array<Scalars['String']['input']> | Scalars['String']['input'];
|
||||
@@ -5542,7 +5549,7 @@ export type UpdateProjectRoleMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateProjectRoleMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', updateRole: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } };
|
||||
export type UpdateProjectRoleMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', updateRole: { __typename?: 'Project', id: string, name: string, description?: string | null, visibility: SimpleProjectVisibility, allowPublicComments: boolean, role?: string | null, createdAt: string, updatedAt: string } } };
|
||||
|
||||
export type CreateServerInviteMutationVariables = Exact<{
|
||||
input: ServerInviteCreateInput;
|
||||
|
||||
Reference in New Issue
Block a user