diff --git a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html
index 74ebad294..bd1762abe 100644
--- a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html
+++ b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html
@@ -38,40 +38,16 @@
@if (favorites().length) {
@for (item of favorites(); track $index) {
-
-
-
-
![]()
-
- {{ item.type }}
-
-
-
-
{{ item?.title || item?.details?.info?.name }}
-
- {{ item?.added_at | date: 'd MMM, HH:mm' }}
-
-
-
+
}
} @else {
diff --git a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.scss b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.scss
index 32ecc6b24..c9fc7c64a 100644
--- a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.scss
+++ b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.scss
@@ -1,4 +1,5 @@
@use '@angular/material' as mat;
+@use '../../../shared/styles/content-grid' as grid;
:host {
display: flex;
@@ -47,107 +48,13 @@
}
.items-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 20px;
+ @include grid.content-grid(140px, 20px);
padding: 16px;
width: 100%;
box-sizing: border-box;
margin: 0;
}
-.item-card {
- cursor: pointer;
- border-radius: 8px;
- overflow: hidden;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
-
- &:hover {
- transform: translateY(-3px);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
- }
-
- .thumbnail-wrapper {
- position: relative;
-
- .poster {
- width: 100%;
- aspect-ratio: 2/3;
- object-fit: cover;
- display: block;
- }
-
- .remove-button {
- position: absolute;
- top: 4px;
- right: 4px;
- z-index: 1;
- background: rgba(0, 0, 0, 0.5);
- width: 28px;
- height: 28px;
- padding: 0;
-
- &:hover {
- background: rgba(0, 0, 0, 0.8);
- }
-
- mat-icon {
- color: white;
- font-size: 18px;
- width: 18px;
- height: 18px;
- }
- }
-
- .type-badge {
- position: absolute;
- top: 6px;
- left: 6px;
- padding: 3px 6px;
- border-radius: 4px;
- font-size: 9px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- background: rgba(0, 0, 0, 0.6);
- color: #fff;
-
- &.live {
- color: #ef5350;
- }
- &.movie {
- color: #42a5f5;
- }
- &.series {
- color: #66bb6a;
- }
- }
- }
-
- .item-info {
- padding: 8px 10px;
-
- h3 {
- margin: 0;
- font-size: 0.8rem;
- font-weight: 500;
- line-height: 1.3;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
-
- .added-date {
- margin: 4px 0 0;
- font-size: 0.7rem;
- opacity: 0.6;
- }
- }
-}
-
.no-items {
display: flex;
flex-direction: column;
diff --git a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts
index 07b31e81d..4750186c0 100644
--- a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts
+++ b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts
@@ -1,12 +1,9 @@
-import { DatePipe } from '@angular/common';
import { Component, input, output } from '@angular/core';
-import { MatIconButton } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon';
-import { MatTooltip } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { PlaylistSwitcherComponent, ResizableDirective } from 'components';
import { CategoryViewComponent } from '../../../xtream-tauri/category-view/category-view.component';
+import { ContentCardComponent } from '../content-card/content-card.component';
@Component({
selector: 'app-favorites-layout',
@@ -17,11 +14,8 @@ import { CategoryViewComponent } from '../../../xtream-tauri/category-view/categ
],
imports: [
CategoryViewComponent,
- DatePipe,
- MatCardModule,
+ ContentCardComponent,
MatIcon,
- MatIconButton,
- MatTooltip,
PlaylistSwitcherComponent,
ResizableDirective,
TranslatePipe,
diff --git a/apps/web/src/app/shared/components/index.ts b/apps/web/src/app/shared/components/index.ts
index abd4bb244..e5775eb61 100644
--- a/apps/web/src/app/shared/components/index.ts
+++ b/apps/web/src/app/shared/components/index.ts
@@ -1 +1,2 @@
+export * from './content-card/content-card.component';
export * from './header/header.component';
diff --git a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html
index 6e3da045b..2e4b318ee 100644
--- a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html
+++ b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html
@@ -32,39 +32,16 @@
@for (item of $any(group).value; track item.id) {
-
-
-
-
![]()
-
- {{ item.type }}
-
-
-
-
{{ item.title }}
-
- {{ item.viewed_at | date : 'd MMM, HH:mm' }}
-
-
-
+
}
@@ -73,35 +50,16 @@
} @else {
@for (item of recentItems(); track item.id) {
-
-
-
-
![]()
-
- {{ item.type }}
-
-
-
-
{{ item.title }}
-
- {{ item.viewed_at | date : 'd MMM, HH:mm' }}
-
-
-
+
}
} } @else {
diff --git a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.scss b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.scss
index b2a151909..b11640007 100644
--- a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.scss
+++ b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.scss
@@ -1,3 +1,5 @@
+@use '../../shared/styles/content-grid' as grid;
+
:host {
display: flex;
flex-direction: column;
@@ -24,101 +26,7 @@
}
.items-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 20px;
-}
-
-.item-card {
- cursor: pointer;
- border-radius: 8px;
- overflow: hidden;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
-
- &:hover {
- transform: translateY(-3px);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
- }
-
- .thumbnail-wrapper {
- position: relative;
-
- .poster {
- width: 100%;
- aspect-ratio: 2/3;
- object-fit: cover;
- display: block;
- }
-
- .remove-button {
- position: absolute;
- top: 4px;
- right: 4px;
- z-index: 1;
- background: rgba(0, 0, 0, 0.5);
- width: 28px;
- height: 28px;
- padding: 0;
-
- &:hover {
- background: rgba(0, 0, 0, 0.8);
- }
-
- mat-icon {
- color: white;
- font-size: 18px;
- width: 18px;
- height: 18px;
- }
- }
-
- .type-badge {
- position: absolute;
- top: 6px;
- left: 6px;
- padding: 3px 6px;
- border-radius: 4px;
- font-size: 9px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- background: rgba(0, 0, 0, 0.6);
- color: #fff;
-
- &.live {
- color: #ef5350;
- }
- &.movie {
- color: #42a5f5;
- }
- &.series {
- color: #66bb6a;
- }
- }
- }
-
- .item-info {
- padding: 8px 10px;
-
- h3 {
- margin: 0;
- font-size: 0.8rem;
- font-weight: 500;
- line-height: 1.3;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
-
- .viewed-at {
- margin: 4px 0 0;
- font-size: 0.7rem;
- opacity: 0.6;
- }
- }
+ @include grid.content-grid(140px, 20px);
}
.no-items {
@@ -180,4 +88,4 @@
background: var(--surface);
}
}
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.ts b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.ts
index 5a8bc51f6..8bdbc4fc0 100644
--- a/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.ts
+++ b/apps/web/src/app/xtream-tauri/recently-viewed/recently-viewed.component.ts
@@ -1,22 +1,21 @@
-import { DatePipe, KeyValuePipe } from '@angular/common';
+import { KeyValuePipe } from '@angular/common';
import { Component, computed, inject, Optional } from '@angular/core';
import { MatButton, MatIconButton } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
-import { MatTooltip } from '@angular/material/tooltip';
import { ActivatedRoute, Router } from '@angular/router';
import groupBy from 'lodash/groupBy';
+import { ContentCardComponent } from '../../shared/components/content-card/content-card.component';
import { XtreamStore } from '../stores/xtream.store';
@Component({
selector: 'app-recently-viewed',
imports: [
- DatePipe,
+ ContentCardComponent,
KeyValuePipe,
MatButton,
MatIcon,
MatIconButton,
- MatTooltip,
],
providers: [],
templateUrl: './recently-viewed.component.html',
@@ -88,8 +87,7 @@ export class RecentlyViewedComponent {
}
}
- removeItem(event: Event, itemId: number) {
- event.stopPropagation();
+ onRemoveItem(itemId: number) {
this.xtreamStore.removeRecentItem({
itemId,
playlistId: this.currentPlaylist().id,
diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.html b/apps/web/src/app/xtream-tauri/search-results/search-results.component.html
index 498bdb4d1..bdec05359 100644
--- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.html
+++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.html
@@ -75,31 +75,13 @@
@for (item of xtreamStore.searchResults(); track item.id) {
-
-
- @if (item.poster_url) {
-
![]()
- } @else {
-
- {{ item.type === 'live' ? 'live_tv' : item.type === 'series' ? 'tv' : 'movie' }}
-
- }
-
- {{ item.type }}
-
-
-
-
{{ item.title }}
-
-
+
}
} } @else if (searchTerm() && searchTerm().length >= 3) {
diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss b/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss
index 823db5e3d..538c1ed92 100644
--- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss
+++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss
@@ -1,3 +1,5 @@
+@use '../../shared/styles/content-grid' as grid;
+
:host {
display: flex;
flex-direction: column;
@@ -80,89 +82,8 @@
overflow-y: auto;
.results-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 20px;
+ @include grid.content-grid(140px, 20px);
padding-bottom: 1rem;
-
- .result-card {
- cursor: pointer;
- border-radius: 8px;
- overflow: hidden;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
-
- &:hover {
- transform: translateY(-3px);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
- }
-
- .thumbnail-wrapper {
- position: relative;
-
- .poster,
- .poster-placeholder {
- width: 100%;
- aspect-ratio: 2/3;
- object-fit: cover;
- display: block;
- }
-
- .poster-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(128, 128, 128, 0.1);
-
- mat-icon {
- font-size: 36px;
- width: 36px;
- height: 36px;
- opacity: 0.25;
- }
- }
-
- .type-badge {
- position: absolute;
- top: 6px;
- left: 6px;
- padding: 3px 6px;
- border-radius: 4px;
- font-size: 9px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- background: rgba(0, 0, 0, 0.6);
- color: #fff;
-
- &.live {
- color: #ef5350;
- }
- &.movie {
- color: #42a5f5;
- }
- &.series {
- color: #66bb6a;
- }
- }
- }
-
- .card-info {
- padding: 8px 10px;
-
- h3 {
- margin: 0;
- font-size: 0.8rem;
- font-weight: 500;
- line-height: 1.3;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- }
- }
}
}
diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts b/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts
index 4f020a352..e60ab18dd 100644
--- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts
+++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts
@@ -10,7 +10,6 @@ import {
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatIconButton } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import {
MAT_DIALOG_DATA,
@@ -26,6 +25,7 @@ import groupBy from 'lodash/groupBy';
import { DatabaseService } from 'services';
import { XtreamContentItem } from '../data-sources/xtream-data-source.interface';
import { ContentType } from '../xtream-state';
+import { ContentCardComponent } from '../../shared/components/content-card/content-card.component';
import { SearchFormComponent } from '../../shared/components/search-form/search-form.component';
import { SearchResultItemComponent } from '../../shared/components/search-result-item/search-result-item.component';
import { XtreamStore } from '../stores/xtream.store';
@@ -38,9 +38,9 @@ interface SearchResultsData {
@Component({
selector: 'app-search-results',
imports: [
+ ContentCardComponent,
FormsModule,
KeyValuePipe,
- MatCardModule,
MatCheckboxModule,
MatDialogModule,
MatFormFieldModule,