Appearance
Sharing Page ​
File Information ​
- Path:
app/pages/[brand_name]/sharing.vue - Route:
/{brand_name}/sharing - Purpose: Manage all share links (active and revoked) with filtering, sorting, and bulk operations
- Layout:
collage-layout - Middleware:
check-url
Overview ​
The Sharing page displays all share links created by the user, including both active and revoked links. It provides comprehensive share link management features including filtering, sorting, bulk selection, revoking, and deleting share links.
Key Features ​
Share Link List
- Table view of all share links
- Active and revoked filter tabs
- Share link details (title, description, created date, creator, source)
- Clickable share URLs
Filtering
- Filter by Active URLs
- Filter by Revoked URLs
- Filter dropdown with options
Sorting
- Sort by Created Date (default)
- Sort by Created By
- Sort by Created Source
- Ascending/descending toggle
- Client-side sorting
Selection & Bulk Actions
- Single and multi-select (Shift+Click support)
- Select all functionality (for revoked URLs)
- Bulk revoke active URLs
- Bulk delete revoked URLs
- Selection count display
Share Link Actions
- Copy share URL
- Revoke active share URL
- Delete revoked share URL
- View share link details
Infinite Scroll Pagination
- Automatic loading on scroll
- Skeleton loaders
- Scroll position management
Components Used ​
ShareTableHeader- Table header with sortingShareTableListItem- Table row componentConfirmationDialog- Confirmation dialog for delete/revokeAsyncIcon- Async icon componentv-skeleton-loader- Skeleton loaders
Composables Used ​
useSharing()- Sharing functionality (app/composables/core/pages/useSharing.ts)useSharingApi()- Sharing API (app/composables/api/useSharingApi.ts)useSnackbar()- Snackbar notificationsuseHelpers()- Helper functions
Data Properties ​
Reactive Data (from useSharing composable) ​
typescript
{
pageScrolling: boolean, // Page scrolling state
contentLoading: boolean, // Initial content loading
select_all_share: boolean, // Select all checkbox state
filter_by: string, // Current filter ('active' | 'revoked')
share_data: ShareItem[], // Share links array
loadMore: boolean, // Load more pagination state
loading: boolean, // Loading state
selectedFile: ShareItem | null, // Selected share item for actions
delete_dialog: boolean, // Delete dialog visibility
deleteMsg: string, // Delete confirmation message
deleteTitle: string, // Delete dialog title
delete_flag: string, // Delete operation flag
sort_by_share: string, // Sort direction ('ASC' | 'DESC')
sort_value_share: string, // Sort field name
shareHeader: ShareHeader[], // Table column definitions
shareMenu: ShareMenuItem[], // Share item menu options
sharedUrlFilter: ShareFilterOption[], // Filter options
shareFiterSelect: ShareFilterOption, // Selected filter
showArrowSortShare: ShareShowArrowSort, // Sort arrow visibility
fileCardKey: number, // File card key for reactivity
scrollContainer: HTMLElement | null, // Scroll container ref
shareContainer: HTMLElement | null, // Share container ref
current_page: number, // Current page number
last_page: number, // Last page number
per_page: number, // Items per page
total: number // Total share links count
}Column Definitions ​
typescript
[
{ name: "Title", slug: "title", enable_sort: false },
{ name: "Description", slug: "description", enable_sort: false },
{ name: "Created Date", slug: "created_at", enable_sort: true },
{ name: "Created By", slug: "user_name", enable_sort: true },
{ name: "Created Source", slug: "generated_source", enable_sort: true }
]Computed Properties ​
selectedItems ​
Returns array of selected share items.
typescript
computed(() =>
share_data.value.filter((item) => item.is_selected === true)
)customDomain ​
Returns custom domain URL if configured.
typescript
computed(() => {
const withHttp = (url: string): string =>
/^https?:\/\//.test(url) ? url : `https://${url}`
const { url: brandUrl, is_domain } = brand || {}
return is_domain && brandUrl ? withHttp(brandUrl) : ""
})Methods ​
fetchShareUrls(flag = "no-sort") ​
Fetches share links from API.
typescript
async function fetchShareUrls(flag: string = "no-sort"): Promise<void> {
try {
if (
loadMore.value ||
loading.value ||
(current_page.value >= last_page.value && flag === "no-sort")
)
return
if (flag !== "sort") {
loadMore.value = current_page.value >= 1
loading.value = current_page.value === 0
current_page.value += 1
}
if (flag === "sort" && scrollContainer.value) {
scrollContainer.value.scrollTop = 0
}
const params = {
workspace_id: workspaceId.value,
page: current_page.value,
total_record: per_page.value || 12,
filter_by: filter_by.value,
}
const { data } = await fetchShareUrlsApi(params)
last_page.value = data.last_page
total.value = data.total
if (["filter", "sort"].includes(flag) && current_page.value === 1) {
share_data.value = []
select_all_share.value = false
}
share_data.value.push(
...data.data.map((item: any) => ({
...item,
is_selected: select_all_share.value,
}))
)
} catch (error) {
console.log("fetchShareUrls", error)
snackbar.showError(getErrorMessage(error))
} finally {
loadMore.value = false
loading.value = false
}
}Parameters:
flag: string- Operation flag: "no-sort", "filter", "sort"
Flow:
- Checks pagination limits
- Sets loading states
- Builds API parameters
- Fetches share URLs
- Updates share data array
- Handles errors
handleScroll(flag, scrollContainerRef, shareContainerRef) ​
Handles scroll events for infinite scroll.
typescript
async function handleScroll(
flag: string = "main",
scrollContainerRef?: HTMLElement | null,
shareContainerRef?: HTMLElement | null
): Promise<void> {
try {
const container =
flag === "main"
? scrollContainerRef || scrollContainer.value
: shareContainerRef || shareContainer.value
if (!container) return
const scrollTop = container.scrollTop
const containerHeight = container.scrollHeight - container.clientHeight
const scrollPercent = (scrollTop / (containerHeight || 1)) * 100
const hasData = share_data.value.length > 0
if (scrollPercent >= 85 && hasData) {
await fetchShareUrls()
} else if (scrollTop >= 36 && hasData) {
pageScrolling.value = true
} else {
pageScrolling.value = false
}
} catch (error) {
console.log("Scroll handler error:", error)
}
}Triggers: Loads more share links when scroll reaches 85% of container height.
toggleSelect(payload) ​
Toggles individual share item selection.
typescript
function toggleSelect(payload: ShareTogglePayload): void {
const { event, file, index } = payload
try {
const isSelected = !file.is_selected
const found = share_data.value.find((obj: any) => obj.id === file.id)
if (found) found.is_selected = isSelected
toggleSelectSharing(
{ share_data: share_data.value },
{
...file,
is_selected: isSelected,
},
index,
event
)
} catch (error) {
console.error("Toggle select error:", error)
lastIndexShare.value = -1
}
}togleSelectAll(val: boolean) ​
Toggles select all state.
typescript
function togleSelectAll(val: boolean): void {
select_all_share.value = val
share_data.value.forEach((obj) => {
obj.is_selected = val
})
lastIndexShare.value = -1
}getSubItems(file, flag) ​
Returns sub-items for table row display.
typescript
function getSubItems(file: ShareItem, flag: string): string[] {
if (flag === "share") {
return [
file.description || "-",
formatDate(file.created_at) || "-",
file.user.name || "-",
file.generated_source === "admin" ? "Backend" : "Frontend",
]
}
return []
}getShareUrl(file) ​
Generates share URL from share item.
typescript
function getShareUrl(file: ShareItem): string | null {
try {
const DAM_FRONTEND_URL = config.public.baseUrl as string
let currentSource = ""
if (import.meta.client) {
currentSource = window.location.origin
}
const queryString = file.share_url.split("?").pop() || ""
const [type, status] = queryString.split("&")
const typeValue = type.replace("type=", "")
const baseUrl =
file.generated_source === "admin"
? currentSource
: customDomain.value || DAM_FRONTEND_URL
let finalUrl = `${baseUrl}/shared-assets/${typeValue}?${status}`
finalUrl = finalUrl.replace(/([^:]\/)\/+/g, "$1")
return finalUrl
} catch (error) {
console.log("getShareUrl", error, file.share_url)
snackbar.showError("Failed to generate share URL")
return null
}
}openRevokeDialog(file) ​
Opens revoke confirmation dialog.
typescript
function openRevokeDialog(file: ShareItem): void {
selectedFile.value = file
delete_flag.value = "revoke-file"
deleteTitle.value = "Revoke Share URL"
deleteMsg.value = "Are you sure you want to revoke shared URL?"
delete_dialog.value = true
}openDeleteAll() ​
Opens bulk delete/revoke confirmation dialog.
typescript
function openDeleteAll(): void {
const multiple = selectedItems.value.length > 1 ? "s" : ""
const isActiveFilter = shareFiterSelect.value.name === "active"
delete_flag.value = isActiveFilter ? "revoke-all" : "delete-share-all"
deleteTitle.value = isActiveFilter
? `Revoke Share URL${multiple}`
: "Confirm Delete"
deleteMsg.value = `Are you sure you want to ${
isActiveFilter ? "revoke" : "delete"
} Shared URL${multiple}?`
delete_dialog.value = true
}openDelShare(file) ​
Opens delete confirmation dialog for revoked share.
typescript
function openDelShare(file: any) {
selectedFile.value = file
delete_flag.value = "delete-share"
deleteTitle.value = `Confirm Delete`
deleteMsg.value = `Are you sure you want to delete the Shared URL?`
delete_dialog.value = true
}deleteFn() ​
Handles delete/revoke operations.
typescript
async function deleteFn(): Promise<void> {
try {
const isBulkDelete = ["delete-share-all", "revoke-all"].includes(
delete_flag.value
)
if (
![
"revoke-file",
"revoke-all",
"delete-share",
"delete-share-all",
].includes(delete_flag.value)
)
return
const selectedIds = isBulkDelete
? selectedItems.value.map(({ id }) => id)
: [selectedFile.value!.id]
const params: Record<string, any> = {
workspace_id: workspaceId.value,
}
if (!isBulkDelete) params.share_url_id = selectedIds
else params.share_url_ids = selectedIds
if (!isBulkDelete) {
const response = await revokeShareUrlsApi(params)
snackbar.showSuccess(response.message)
} else {
const response = await deleteShareUrlsApi(params)
snackbar.showSuccess(response.message)
}
share_data.value = share_data.value.filter(
(e) => !selectedIds.includes(e.id)
)
if (!share_data.value.length) {
await fetchShareUrls()
}
} catch (error) {
console.log("deleteFn", error)
snackbar.showError(getErrorMessage(error))
} finally {
delete_dialog.value = false
delete_flag.value = ""
}
}shareFilterUpdate(payload) ​
Handles filter change.
typescript
async function shareFilterUpdate(payload: ShareFilterOption): Promise<void> {
if (!payload) return
try {
filter_by.value = payload.name
// Reset sort indicators
Object.keys(showArrowSortShare.value).forEach((key) => {
showArrowSortShare.value[key as keyof ShareShowArrowSort] = false
})
current_page.value = 0
sort_by_share.value = "DESC"
sort_value_share.value = "created_at"
shareHeader.value.forEach((item) => (item.togle_sort = false))
showArrowSortShare.value.created_at = true
await fetchShareUrls("filter")
lastIndexShare.value = -1
} catch (error) {
console.error("Filter update error:", error)
}
}shareEmbedSort(payload) ​
Handles column sorting (client-side).
typescript
async function shareEmbedSort(payload: any) {
const field = payload.field
try {
if (payload.flag === "share-url") {
for (const key in showArrowSortShare.value) {
showArrowSortShare.value[key] = false
}
showArrowSortShare.value[field] = true
sort_value_share.value = field
sort_by_share.value = payload.toggle ? "ASC" : "DESC"
// Perform client-side sorting
share_data.value.sort((a: any, b: any) => {
let fieldA = a[field]
let fieldB = b[field]
// Handle date fields
if (field === "modified_at" || field === "created_at") {
fieldA = new Date(fieldA as string)
fieldB = new Date(fieldB as string)
}
// Handle source field
if (field === "generated_source") {
fieldA = fieldA === "user" ? "Frontend" : "Admin"
fieldB = fieldB === "user" ? "Frontend" : "Admin"
}
// Handle user name field
if (field === "user_name") {
fieldA = a?.user?.name || ""
fieldB = b?.user?.name || ""
}
// Compare based on sort direction
if (sort_by_share.value === "ASC") {
return fieldA > fieldB ? 1 : fieldA < fieldB ? -1 : 0
} else {
return fieldA < fieldB ? 1 : fieldA > fieldB ? -1 : 0
}
})
}
} catch (error) {
console.log(error)
}
}cancelDelDialog() ​
Closes delete dialog.
typescript
function cancelDelDialog(): void {
delete_dialog.value = false
delete_flag.value = ""
}User Interactions ​
Filter Management
- Filter by Active URLs
- Filter by Revoked URLs
- Filter dropdown selection
Sorting
- Sort by Created Date (default)
- Sort by Created By
- Sort by Created Source
- Click column headers to sort (client-side)
- Visual sort indicators
Selection
- Single click to select
- Shift+Click for range selection
- Select all functionality (for revoked URLs)
- Selection count display
Share Link Actions
- Copy share URL
- Revoke active share URL
- Delete revoked share URL
- View share link details
Bulk Actions
- Bulk revoke active URLs
- Bulk delete revoked URLs
- Confirmation dialogs for destructive actions
Real-time Features ​
- Share link data loaded on demand
- No real-time updates (manual refresh required)
Styling ​
- Uses custom CSS from
assets/scss/custom.scss - Responsive breakpoints for mobile/tablet/desktop
- Skeleton loaders for loading states
- Table view styling
- Custom scrollbar styling
- Filter dropdown styling
Watchers ​
None - uses event handlers and lifecycle hooks.
Lifecycle Hooks ​
onMounted ​
Initializes share links data.
typescript
onMounted(async () => {
await fetchShareUrls()
contentLoading.value = false
})API Endpoints ​
Fetch Share URLs ​
- Endpoint:
GET /share-urls(viafetchShareUrlsApi()) - Parameters:typescript
{ workspace_id: string, page: number, total_record: number, filter_by: 'active' | 'revoked' } - Response:typescript
{ data: ShareItem[], last_page: number, total: number }
Revoke Share URLs ​
- Endpoint:
POST /share-urls/revoke(viarevokeShareUrlsApi()) - Parameters:typescript
{ workspace_id: string, share_url_id?: number | number[], share_url_ids?: number[] } - Response: Success message
Delete Share URLs ​
- Endpoint:
DELETE /share-urls(viadeleteShareUrlsApi()) - Parameters:typescript
{ workspace_id: string, share_url_id?: number | number[], share_url_ids?: number[] } - Response: Success message
User Flow ​
- User navigates to sharing page
- Page fetches share links (default: active)
- User can:
- Filter by Active or Revoked URLs
- Sort by columns (client-side)
- Select share links (single or multi-select)
- Copy share URL
- Revoke active share URL
- Delete revoked share URL
- Bulk revoke/delete selected links
- Scroll for more links
- Infinite scroll loads more share links automatically
- Confirmation dialogs for destructive actions
Performance Optimizations ​
- Pagination: Loads share links in pages (36 per page)
- Client-Side Sorting: Sorts loaded links without API call
- Scroll Throttling: Scroll events handled efficiently
- Lazy Loading: Data loaded on demand
Notes for Development ​
- Active URLs can be revoked (made inactive)
- Revoked URLs can be deleted permanently
- Select all only available for revoked URLs
- Share URLs generated based on source (admin vs user)
- Custom domain support for share URLs
- Client-side sorting only affects loaded links
- Confirmation required for all destructive actions
Related Documentation ​
- useSharing Composable - Sharing composable details
- Sharing API - Share URL API endpoints
- ShareTableHeader Component - Table header component
- ShareTableListItem Component - Table row component
- ConfirmationDialog Component - Confirmation dialog