Skip to content

Add shared collection feature: implement sharing functionality with copyable links and display for shared collections - #22

Merged
ebulgakov merged 2 commits into
mainfrom
feature/copy-url
Jul 31, 2026
Merged

Add shared collection feature: implement sharing functionality with copyable links and display for shared collections#22
ebulgakov merged 2 commits into
mainfrom
feature/copy-url

Conversation

@ebulgakov

@ebulgakov ebulgakov commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added shareable links for collections, including one-click copying with confirmation feedback.
    • Added public shared collection pages accessible through shareable URLs.
    • Shared collections display titles, descriptions, images, and links that open in a new tab.
    • Added localized English and Russian text for sharing, loading, empty, and not-found states.

…opyable links and display for shared collections
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linkfolio Ready Ready Preview Jul 31, 2026 3:02pm

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ebulgakov, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 881cba45-099a-4848-bfa4-9d69e7941ebf

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc0614 and 90ddfd5.

📒 Files selected for processing (5)
  • app/features/collection-share-link/index.ts
  • app/features/collection-share-link/model/use-collection-share-link.ts
  • app/features/collection-share-link/ui/collection-share-link.vue
  • app/features/shared-collection/ui/shared-link-card.vue
  • server/api/shared/[slug]/links/index.get.ts
📝 Walkthrough

Walkthrough

Adds public slug-based collection APIs, client data access, collection sharing controls, clipboard feedback, localized messages, and a shared collection page with link cards.

Changes

Public collection sharing

Layer / File(s) Summary
Shared collection API
server/utils/shared-link-select.ts, server/api/shared/...
Adds validated endpoints for shared collection metadata and ordered public links. The database projection exposes only public link fields.
Shared data access
app/shared/api/shared-collections.ts, app/shared/api/use-shared-collection.ts, app/shared/api/index.ts
Defines shared collection models and fetch helpers. Adds useSharedCollection with typed loading, refresh, error, and not-found state.
Collection sharing controls
app/shared/lib/use-clipboard.ts, app/features/collection-share-link/..., app/pages/collections/[id]/index.vue, i18n/locales/{en,ru}.json
Adds share-link rendering, clipboard copying with timed state, collection-page integration, and localized copy feedback.
Shared collection page
app/features/shared-collection/..., app/pages/shared/[slug].vue, i18n/locales/{en,ru}.json
Adds the slug-based shared collection page and link cards. The page renders collection metadata, empty states, localized errors, and links that open in new tabs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant SharedPage
  participant useSharedCollection
  participant SharedAPI
  participant Database

  Visitor->>SharedPage: Open shared collection slug
  SharedPage->>useSharedCollection: Fetch collection data
  useSharedCollection->>SharedAPI: Request collection metadata and links
  SharedAPI->>Database: Query shared collection and public links
  Database-->>SharedAPI: Return selected records
  SharedAPI-->>useSharedCollection: Return API responses
  useSharedCollection-->>SharedPage: Expose data and state
  SharedPage-->>Visitor: Render collection and link cards
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding shared collection functionality with copyable links and shared-collection display.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/copy-url

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
app/shared/api/use-shared-collection.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import the shared type through the API barrel.

Replace the sibling import with import type { SharedCollection } from "~/shared/api";. Keep import type so this change does not add a runtime barrel dependency.

As per coding guidelines, "Import shared modules through their segment barrels (~/shared/api, ~/shared/ui, or ~/shared/lib) rather than deep paths."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/api/use-shared-collection.ts` at line 1, Update the
SharedCollection type import in use-shared-collection.ts to use the shared API
barrel via the ~/shared/api alias instead of the sibling deep path, while
preserving import type so no runtime dependency is introduced.

Source: Coding guidelines

app/features/shared-collection/ui/shared-link-card.vue (1)

13-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add alt text to the link preview image.

v-img at Line 13 has no alt attribute. Screen readers cannot describe the preview image. Use displayTitle or the link URL as alt text.

♿ Proposed fix
-    <v-img v-if="link.imageUrl" :src="link.imageUrl" height="160" cover />
+    <v-img v-if="link.imageUrl" :src="link.imageUrl" :alt="displayTitle" height="160" cover />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/features/shared-collection/ui/shared-link-card.vue` around lines 13 - 16,
Update the v-img element in the shared link card to include alt text, using
displayTitle when available and the link URL as the fallback, while preserving
the existing conditional rendering and image properties.
app/pages/shared/[slug].vue (2)

11-20: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip the links request when the collection fetch already failed.

Line 15 awaits useSharedCollection(slug) before Line 18 starts the links useAsyncData call. When collectionError is set (404 or otherwise), the page still issues a request to /api/shared/${slug}/links. That request hits an endpoint scoped to the same unusable slug and wastes a round trip.

Skip the links fetch when the collection already failed.

⚡ Proposed fix
 const requestFetch = useRequestFetch();
-const { data: links, error: linksError } = await useAsyncData(`shared-links-${slug}`, () =>
-  requestFetch<SharedLinkItem[]>(`/api/shared/${slug}/links`)
-);
+const { data: links, error: linksError } = await useAsyncData(`shared-links-${slug}`, () =>
+  collectionError.value ? Promise.resolve([]) : requestFetch<SharedLinkItem[]>(`/api/shared/${slug}/links`)
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/pages/shared/`[slug].vue around lines 11 - 20, Update the links-loading
flow after useSharedCollection(slug) so useAsyncData for shared-links-${slug} is
not invoked when collectionError is set. Preserve the existing links request and
response handling when the collection fetch succeeds, and keep linksError
available to the page.

9-9: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Validate the slug route parameter type.

Line 9 casts route.params.slug as string without runtime validation. For a single dynamic segment [slug].vue, Vue Router always returns a string here, so this is a low-risk assumption, but the cast itself provides no safety if the route structure changes later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/pages/shared/`[slug].vue at line 9, Update the slug handling near the
route parameter extraction to validate that route.params.slug is a string at
runtime instead of relying solely on the `as string` cast, while preserving the
existing behavior for valid single-segment routes and handling invalid values
safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/features/collection-share-link/ui/collection-share-link.vue`:
- Around line 6-13: Move share URL construction and clipboard orchestration from
the collection-share-link UI component into a model composable, then have the
component consume its exposed values and actions while remaining presentational.
Export the model composable alongside the UI component from
app/features/collection-share-link/index.ts at line 1, and update
app/features/collection-share-link/ui/collection-share-link.vue lines 6-13
accordingly.
- Around line 11-13: Update onCopyClick to be asynchronous, await
copy(shareUrl.value), and catch rejected clipboard writes. On failure, set a
distinct non-copied failure state or provide the existing manual-copy fallback
so the rejection is handled without becoming unhandled.

In `@server/api/shared/`[slug]/links/index.get.ts:
- Around line 17-31: Replace the separate collection lookup and link query with
one query rooted at collections, filtering by both parsedSlug.data and
collections.shared, then left-joining collectionItems and urls. Update the
result handling so no matching collection still throws the existing 404, while a
valid shared collection with no links returns an empty array; preserve
sharedLinkSelection and position ordering.

---

Nitpick comments:
In `@app/features/shared-collection/ui/shared-link-card.vue`:
- Around line 13-16: Update the v-img element in the shared link card to include
alt text, using displayTitle when available and the link URL as the fallback,
while preserving the existing conditional rendering and image properties.

In `@app/pages/shared/`[slug].vue:
- Around line 11-20: Update the links-loading flow after
useSharedCollection(slug) so useAsyncData for shared-links-${slug} is not
invoked when collectionError is set. Preserve the existing links request and
response handling when the collection fetch succeeds, and keep linksError
available to the page.
- Line 9: Update the slug handling near the route parameter extraction to
validate that route.params.slug is a string at runtime instead of relying solely
on the `as string` cast, while preserving the existing behavior for valid
single-segment routes and handling invalid values safely.

In `@app/shared/api/use-shared-collection.ts`:
- Line 1: Update the SharedCollection type import in use-shared-collection.ts to
use the shared API barrel via the ~/shared/api alias instead of the sibling deep
path, while preserving import type so no runtime dependency is introduced.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e5004634-5b50-40d1-903e-3f46610eef55

📥 Commits

Reviewing files that changed from the base of the PR and between d2353ed and 5fc0614.

📒 Files selected for processing (16)
  • app/features/collection-share-link/index.ts
  • app/features/collection-share-link/ui/collection-share-link.vue
  • app/features/shared-collection/index.ts
  • app/features/shared-collection/ui/shared-link-card.vue
  • app/pages/collections/[id]/index.vue
  • app/pages/shared/[slug].vue
  • app/shared/api/index.ts
  • app/shared/api/shared-collections.ts
  • app/shared/api/use-shared-collection.ts
  • app/shared/lib/index.ts
  • app/shared/lib/use-clipboard.ts
  • i18n/locales/en.json
  • i18n/locales/ru.json
  • server/api/shared/[slug].get.ts
  • server/api/shared/[slug]/links/index.get.ts
  • server/utils/shared-link-select.ts

Comment thread app/features/collection-share-link/ui/collection-share-link.vue Outdated
Comment thread app/features/collection-share-link/ui/collection-share-link.vue Outdated
Comment thread server/api/shared/[slug]/links/index.get.ts Outdated
…race

- Move share-URL/clipboard orchestration out of collection-share-link.vue
  into a model composable (FSD boundary), handling clipboard write
  rejections with an error snackbar instead of an unhandled rejection.
- Rewrite the public shared-links endpoint as a single left-joined query
  rooted at collections, so the shared check and link read are atomic -
  closes a race where unsharing between two separate queries could leak
  links to an anonymous caller.
- Add alt text to the shared link card's preview image.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant