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
150 changes: 150 additions & 0 deletions layers/marketplace/pages/integrations/[integration].vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,38 @@ useSchemaOrg([
/>
</div>
</section>
<!-- External Resources Section -->
<section v-if="integration?.external_resources?.length" id="external-resources">
<BaseHeading tag="h2" content="Documentation & Resources" size="medium" />
<div class="external-resources-list">
<a
v-for="resource in integration.external_resources"
:key="resource.id"
:href="resource.url"
target="_blank"
rel="noopener noreferrer"
class="external-resource-card"
>
<div v-if="resource.image" class="resource-image">
<BaseDirectusImage
:width="120"
:height="120"
:uuid="typeof resource.image === 'string' ? resource.image : resource.image?.id"
:alt="resource.title"
/>
</div>
<div v-else class="resource-image-placeholder">
<BaseIcon name="description" size="large" />
</div>
<div class="resource-content">
<h3 class="resource-title">{{ resource.title }}</h3>
<p v-if="resource.description" class="resource-description">{{ resource.description }}</p>
</div>
<BaseIcon name="open_in_new" class="external-link-icon" size="small" />
</a>
</div>
</section>
<!-- Overview Section -->
<section id="overview">
<BaseHeading tag="h2" content="Overview" size="medium" />
<BaseText v-if="integration?.content" :content="integration?.content" color="foreground" class="mt-4" />
Expand Down Expand Up @@ -317,6 +349,124 @@ useSchemaOrg([
margin-block-start: var(--space-6);
}

.external-resources-list {
display: flex;
flex-direction: column;
gap: var(--space-4);
margin-block-start: var(--space-6);
}

.external-resource-card {
display: flex;
align-items: center;
gap: var(--space-4);
padding: var(--space-4);
border-radius: var(--rounded-lg);
border: 1px solid var(--gray-200);
background-color: var(--background);
color: var(--foreground);
text-decoration: none;
transition: all var(--duration-200) var(--ease-out);
position: relative;

&:hover {
border-color: var(--gray-300);
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
transform: translateY(-2px);

.resource-title {
color: var(--primary);
}

.external-link-icon {
color: var(--primary);
}
}

&:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-100);
}

&:focus-visible {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-100);
}
}

.resource-image {
flex-shrink: 0;
width: 120px;
height: 120px;
border-radius: var(--rounded-md);
overflow: hidden;
background-color: var(--gray-100);
display: flex;
align-items: center;
justify-content: center;

@media (width <= 30rem) {
display: none;
}

img {
width: 100%;
height: 100%;
object-fit: cover;
}
}

.resource-image-placeholder {
flex-shrink: 0;
width: 120px;
height: 120px;
border-radius: var(--rounded-md);
background-color: var(--gray-100);
display: flex;
align-items: center;
justify-content: center;
color: var(--gray-400);

@media (width <= 30rem) {
display: none;
}
}

.resource-content {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--space-2);
min-width: 0;
}

.resource-title {
font-size: var(--font-size-lg);
font-weight: 600;
line-height: var(--line-height-lg);
margin: 0;
color: var(--foreground);
transition: color var(--duration-200) var(--ease-out);
}

.resource-description {
color: var(--gray-600);
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
margin: 0;
}

.external-link-icon {
position: absolute;
top: var(--space-4);
right: var(--space-4);
flex-shrink: 0;
color: var(--gray-400);
transition: color var(--duration-200) var(--ease-out);
}

.desktop-only {
display: none;

Expand Down
41 changes: 37 additions & 4 deletions layers/marketplace/server/api/integrations/[integration].get.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { MarketplaceIntegration, MarketplaceExtension } from '~/types/marketplace';
import type { IntegrationExternalResource } from '~/types/schema/marketplace/integration-external-resource';
import { arrayToString } from '~/utils/arrayToString';
import { isUuid } from '~/layers/marketplace/server/utils/isUuid';
import { typesenseServer } from '~/layers/marketplace/server/services/typesense';
import { consola } from 'consola';

interface IntegrationWithExtensions extends MarketplaceIntegration {
extensionDetails?: MarketplaceExtension[];
external_resources?: IntegrationExternalResource[];
}

const config = useRuntimeConfig();
Expand All @@ -27,14 +30,38 @@ export default defineEventHandler(
const response = await $fetch<{ data?: MarketplaceIntegration[] } | MarketplaceIntegration[]>(
`${directusUrl}/items/integrations`,
{
params: {
fields: ['*'],
filter,
query: {
fields: [
'*',
'external_resources.id',
'external_resources.title',
'external_resources.url',
'external_resources.description',
'external_resources.image',
'external_resources.sort',
'external_resources.status',
],
filter: {
...filter,
status: {
_eq: 'published',
},
},
deep: {
external_resources: {
_filter: {
status: {
_eq: 'published',
},
},
_sort: ['sort'],
},
},
},
},
);

const integration = Array.isArray(response) ? response[0] : (response as any)?.data[0] || [];
const integration = Array.isArray(response) ? response[0] : (response as any)?.data?.[0] || null;

if (!integration) {
throw createError({
Expand Down Expand Up @@ -66,9 +93,15 @@ export default defineEventHandler(
}
}

const externalResources: IntegrationExternalResource[] =
integration.external_resources && Array.isArray(integration.external_resources)
? integration.external_resources
: [];

return {
...integration,
extensionDetails,
external_resources: externalResources,
};
} catch (error) {
consola.error('Integration detail API error:', error);
Expand Down
9 changes: 9 additions & 0 deletions types/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ export interface MarketplaceIntegration {
}>;
content: string;
extensionDetails?: MarketplaceExtension[];
external_resources?: Array<{
id: string;
title: string;
url: string;
description?: string | null;
image?: File | string | null;
sort?: number | null;
status?: string | null;
}>;
}

export interface MarketplaceRequest {
Expand Down
14 changes: 14 additions & 0 deletions types/schema/marketplace/integration-external-resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { File } from '../system';

export interface IntegrationExternalResource {
/** @required */
id: string;
sort?: number | null;
/** @required */
title: string;
/** @required */
url: string;
description?: string | null;
image?: File | string | null;
integration?: string | null;
}