mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 15:05:35 +00:00
feat: add shared collection page
This commit is contained in:
24
app/controllers/shared_collections_controller.ts
Normal file
24
app/controllers/shared_collections_controller.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Visibility } from '#enums/visibility';
|
||||||
|
import Collection from '#models/collection';
|
||||||
|
import { getSharedCollectionValidator } from '#validators/shared_collection';
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
|
||||||
|
export default class SharedCollectionsController {
|
||||||
|
async index({ request, inertia }: HttpContext) {
|
||||||
|
const { params } = await request.validateUsing(
|
||||||
|
getSharedCollectionValidator
|
||||||
|
);
|
||||||
|
|
||||||
|
const collection = await this.getSharedCollectionById(params.id);
|
||||||
|
return inertia.render('shared', { collection });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSharedCollectionById(id: Collection['id']) {
|
||||||
|
return await Collection.query()
|
||||||
|
.where('id', id)
|
||||||
|
.andWhere('visibility', Visibility.PUBLIC)
|
||||||
|
.preload('links')
|
||||||
|
.preload('author')
|
||||||
|
.firstOrFail();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ export default class Collection extends AppBaseModel {
|
|||||||
@column()
|
@column()
|
||||||
declare authorId: number;
|
declare authorId: number;
|
||||||
|
|
||||||
@belongsTo(() => User, { foreignKey: 'author_id' })
|
@belongsTo(() => User, { foreignKey: 'authorId' })
|
||||||
declare author: BelongsTo<typeof User>;
|
declare author: BelongsTo<typeof User>;
|
||||||
|
|
||||||
@hasMany(() => Link)
|
@hasMany(() => Link)
|
||||||
|
|||||||
11
app/validators/shared_collection.ts
Normal file
11
app/validators/shared_collection.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import vine from '@vinejs/vine';
|
||||||
|
|
||||||
|
const params = vine.object({
|
||||||
|
id: vine.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getSharedCollectionValidator = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -7,9 +7,10 @@ import Footer from '~/components/footer/footer';
|
|||||||
import useActiveCollection from '~/hooks/use_active_collection';
|
import useActiveCollection from '~/hooks/use_active_collection';
|
||||||
|
|
||||||
export interface CollectionHeaderProps {
|
export interface CollectionHeaderProps {
|
||||||
openNavigationItem: ReactNode;
|
|
||||||
openCollectionItem: ReactNode;
|
|
||||||
showButtons: boolean;
|
showButtons: boolean;
|
||||||
|
showControls?: boolean;
|
||||||
|
openNavigationItem?: ReactNode;
|
||||||
|
openCollectionItem?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionContainerStyle = styled.div({
|
const CollectionContainerStyle = styled.div({
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const CollectionHeaderWrapper = styled.div(({ theme }) => ({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
paddingInline: `${paddingLeft} ${paddingRight}`,
|
paddingInline: `${paddingLeft} ${paddingRight}`,
|
||||||
marginBottom: 0,
|
marginBottom: '0.5em',
|
||||||
|
|
||||||
[`@media (max-width: ${theme.media.tablet})`]: {
|
[`@media (max-width: ${theme.media.tablet})`]: {
|
||||||
paddingInline: 0,
|
paddingInline: 0,
|
||||||
@@ -53,9 +53,10 @@ const LinksCount = styled.div(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export default function CollectionHeader({
|
export default function CollectionHeader({
|
||||||
|
showButtons,
|
||||||
|
showControls = true,
|
||||||
openNavigationItem,
|
openNavigationItem,
|
||||||
openCollectionItem,
|
openCollectionItem,
|
||||||
showButtons,
|
|
||||||
}: CollectionHeaderProps) {
|
}: CollectionHeaderProps) {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { activeCollection } = useActiveCollection();
|
const { activeCollection } = useActiveCollection();
|
||||||
@@ -75,7 +76,9 @@ export default function CollectionHeader({
|
|||||||
visibility={visibility}
|
visibility={visibility}
|
||||||
/>
|
/>
|
||||||
</CollectionName>
|
</CollectionName>
|
||||||
<CollectionControls collectionId={activeCollection.id} />
|
{showControls && (
|
||||||
|
<CollectionControls collectionId={activeCollection.id} />
|
||||||
|
)}
|
||||||
{showButtons && openCollectionItem && openCollectionItem}
|
{showButtons && openCollectionItem && openCollectionItem}
|
||||||
</CollectionHeaderStyle>
|
</CollectionHeaderStyle>
|
||||||
{activeCollection.description && <CollectionDescription />}
|
{activeCollection.description && <CollectionDescription />}
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ const LinkListStyle = styled.ul({
|
|||||||
overflowY: 'scroll',
|
overflowY: 'scroll',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function LinkList({ links }: { links: Link[] }) {
|
export default function LinkList({
|
||||||
|
links,
|
||||||
|
showControls = true,
|
||||||
|
}: {
|
||||||
|
links: Link[];
|
||||||
|
showControls?: boolean;
|
||||||
|
}) {
|
||||||
if (links.length === 0) {
|
if (links.length === 0) {
|
||||||
return <NoLink />;
|
return <NoLink />;
|
||||||
}
|
}
|
||||||
@@ -25,7 +31,7 @@ export default function LinkList({ links }: { links: Link[] }) {
|
|||||||
return (
|
return (
|
||||||
<LinkListStyle>
|
<LinkListStyle>
|
||||||
{sortByCreationDate(links).map((link) => (
|
{sortByCreationDate(links).map((link) => (
|
||||||
<LinkItem link={link} key={link.id} showUserControls />
|
<LinkItem link={link} key={link.id} showUserControls={showControls} />
|
||||||
))}
|
))}
|
||||||
</LinkListStyle>
|
</LinkListStyle>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { ItemLink } from '~/components/dashboard/side_nav/nav_item';
|
import { ItemExternalLink } from '~/components/dashboard/side_nav/nav_item';
|
||||||
|
|
||||||
const FavoriteItem = styled(ItemLink)(({ theme }) => ({
|
const FavoriteItem = styled(ItemExternalLink)(({ theme }) => ({
|
||||||
backgroundColor: theme.colors.secondary,
|
backgroundColor: theme.colors.secondary,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Link } from '@inertiajs/react';
|
import { Link } from '@inertiajs/react';
|
||||||
|
import ExternalLink from '~/components/common/external_link';
|
||||||
import { rgba } from '~/lib/color';
|
import { rgba } from '~/lib/color';
|
||||||
|
|
||||||
export const Item = styled.div(({ theme }) => ({
|
export const Item = styled.div(({ theme }) => ({
|
||||||
@@ -27,3 +28,4 @@ export const Item = styled.div(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const ItemLink = Item.withComponent(Link);
|
export const ItemLink = Item.withComponent(Link);
|
||||||
|
export const ItemExternalLink = Item.withComponent(ExternalLink);
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import styled from '@emotion/styled';
|
|||||||
import { IoEarthOutline } from 'react-icons/io5';
|
import { IoEarthOutline } from 'react-icons/io5';
|
||||||
|
|
||||||
const VisibilityStyle = styled.span(({ theme }) => ({
|
const VisibilityStyle = styled.span(({ theme }) => ({
|
||||||
|
userSelect: 'none',
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
fontSize: '0.6em',
|
fontSize: '0.6em',
|
||||||
color: theme.colors.lightBlue,
|
color: theme.colors.primary,
|
||||||
border: `1px solid ${theme.colors.lightBlue}`,
|
border: `1px solid ${theme.colors.primary}`,
|
||||||
borderRadius: '50px',
|
borderRadius: '50px',
|
||||||
padding: '0.15em 0.65em',
|
padding: '0.15em 0.65em',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ function DashboardPage(props: Readonly<DashboardPageProps>) {
|
|||||||
}
|
}
|
||||||
}, [isMobile, isTablet, closeCollectionList, closeNavigation]);
|
}, [isMobile, isTablet, closeCollectionList, closeNavigation]);
|
||||||
|
|
||||||
console.log(isMobile, isTablet, isNavigationOpen, isCollectionListOpen);
|
|
||||||
return (
|
return (
|
||||||
<DashboardProviders
|
<DashboardProviders
|
||||||
collections={props.collections}
|
collections={props.collections}
|
||||||
|
|||||||
24
inertia/pages/shared.tsx
Normal file
24
inertia/pages/shared.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import CollectionHeader from '~/components/dashboard/collection/header/collection_header';
|
||||||
|
import LinkList from '~/components/dashboard/link/link_list';
|
||||||
|
import ContentLayout from '~/components/layouts/content_layout';
|
||||||
|
import { ActiveCollectionContext } from '~/contexts/active_collection_context';
|
||||||
|
import { CollectionWithLinks } from '~/types/app';
|
||||||
|
|
||||||
|
const SharedCollectionPage = ({
|
||||||
|
collection,
|
||||||
|
}: {
|
||||||
|
collection: CollectionWithLinks;
|
||||||
|
}) => (
|
||||||
|
<ActiveCollectionContext.Provider
|
||||||
|
value={{ activeCollection: collection, setActiveCollection: () => {} }}
|
||||||
|
>
|
||||||
|
<CollectionHeader showButtons={false} showControls={false} />
|
||||||
|
<LinkList links={collection.links} showControls={false} />
|
||||||
|
</ActiveCollectionContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
SharedCollectionPage.layout = (page: ReactNode) => (
|
||||||
|
<ContentLayout css={{ width: '900px' }} children={page} />
|
||||||
|
);
|
||||||
|
export default SharedCollectionPage;
|
||||||
@@ -23,7 +23,7 @@ export const lightTheme: Theme = {
|
|||||||
white: '#ffffff',
|
white: '#ffffff',
|
||||||
|
|
||||||
lightGrey: '#dadce0',
|
lightGrey: '#dadce0',
|
||||||
grey: '#888888',
|
grey: '#777777',
|
||||||
|
|
||||||
lightestBlue,
|
lightestBlue,
|
||||||
lightBlue,
|
lightBlue,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import './routes/admin.js';
|
||||||
import './routes/app.js';
|
import './routes/app.js';
|
||||||
import './routes/auth.js';
|
import './routes/auth.js';
|
||||||
import './routes/collection.js';
|
import './routes/collection.js';
|
||||||
import './routes/favicon.js';
|
import './routes/favicon.js';
|
||||||
import './routes/link.js';
|
import './routes/link.js';
|
||||||
import './routes/search.js';
|
import './routes/search.js';
|
||||||
import './routes/admin.js';
|
import './routes/shared_collection.js';
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import router from '@adonisjs/core/services/router';
|
|||||||
const CollectionsController = () =>
|
const CollectionsController = () =>
|
||||||
import('#controllers/collections_controller');
|
import('#controllers/collections_controller');
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes for authenticated users
|
|
||||||
*/
|
|
||||||
router
|
router
|
||||||
.group(() => {
|
.group(() => {
|
||||||
router.get('/dashboard', [CollectionsController, 'index']).as('dashboard');
|
router.get('/dashboard', [CollectionsController, 'index']).as('dashboard');
|
||||||
|
|||||||
6
start/routes/shared_collection.ts
Normal file
6
start/routes/shared_collection.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import router from '@adonisjs/core/services/router';
|
||||||
|
|
||||||
|
const SharedCollectionsController = () =>
|
||||||
|
import('#controllers/shared_collections_controller');
|
||||||
|
|
||||||
|
router.get('/shared/:id', [SharedCollectionsController, 'index']).as('shared');
|
||||||
Reference in New Issue
Block a user