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
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@

<div id="root" style="opacity:0"></div>

<script>performance.mark('html:start');</script>
<script type="module" src="/src/index.tsx"></script>
<script>
function showApp() {
Expand Down
6 changes: 6 additions & 0 deletions src/components/app-ready.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ export default function AppReady() {
useEffect(() => {
requestAnimationFrame(() =>
requestAnimationFrame(() => {
performance.mark('react:first-paint');
performance.measure('html→entry','html:start','entry:begin');
performance.measure('entry→react','entry:begin','react:first-paint');
console.table(performance.getEntriesByType('measure').map(m=>({
name:m.name, ms: Math.round(m.duration)
})));
window.dispatchEvent(new Event('app:ready'));
})
);
Expand Down
2 changes: 2 additions & 0 deletions src/components/publication-detail-main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
} from '@src/graphql/generated/hooks.tsx';
import { resolveSrc } from '@src/utils/image.ts';
import { useBookmarks } from '@src/hooks/use-bookmark.ts';
import PublicationShare from '@src/sections/publication/components/publication-share.tsx';

// ----------------------------------------------------------------------

Expand Down Expand Up @@ -413,6 +414,7 @@ export default function PublicationDetailMain({
</>
)}
</Button>
<PublicationShare post={post} />
</Stack>
</m.div>
</Box>
Expand Down
228 changes: 228 additions & 0 deletions src/components/share-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Popover from '@mui/material/Popover';
import Stack from '@mui/material/Stack';
import { SxProps, Theme } from '@mui/material/styles';
import Iconify from '@src/components/iconify';
import { notifyError, notifySuccess } from '@src/libs/notifications/internal-notifications.ts';
import { SUCCESS } from '@src/libs/notifications/success.ts';
import { ERRORS } from '@src/libs/notifications/errors';

// ----------------------------------------------------------------------

interface BaseLink {
icon: string;
label: string;
url: string;
}

interface ExtraIcon {
key: string;
icon: string;
}

interface ShareButtonProps {
placeholder: string;
pathPrefix: string;
targetId: string;
shareLinks: BaseLink[];
socialMediaList?: ExtraIcon[];
socialMediaUrls?: Record<string, string | undefined>;
templateUrl: string;
buttonVariant?: 'text' | 'outlined';
buttonSx?: SxProps<Theme>;
}

// ----------------------------------------------------------------------

const ShareButton: FC<ShareButtonProps> = ({
placeholder,
pathPrefix,
targetId,
shareLinks,
templateUrl,
socialMediaList,
socialMediaUrls,
buttonVariant = 'outlined',
buttonSx,
}) => {
const [openTooltipShare, setOpenTooltipShare] = useState(false);
const navRefSocial = useRef<HTMLButtonElement | null>(null);

const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const openPopover = Boolean(anchorEl);

const replacePlaceholder = (url: string) =>
url.replace(placeholder, `${pathPrefix}${targetId}`);

const pageUrl = templateUrl.replace(placeholder, `${pathPrefix}${targetId}`);

const handlePopoverOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(e.currentTarget);
};

const handlePopoverClose = () => setAnchorEl(null);

const handleOpenShare = useCallback(() => setOpenTooltipShare(true), []);
const handleCloseShare = useCallback(() => setOpenTooltipShare(false), []);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(pageUrl);
notifySuccess(SUCCESS.LINK_COPIED_TO_CLIPBOARD);
} catch {
notifyError(ERRORS.LINK_COPIED_ERROR);
}
};

useEffect(() => {
if (openPopover) handleCloseShare();
}, [openPopover, handleCloseShare]);

return (
<>
{socialMediaList?.map(
({ key, icon }) =>
socialMediaUrls?.[key] && (
<Button
key={`link-${key}`}
component="a"
href={socialMediaUrls[key]!}

Check failure on line 91 in src/components/share-button.tsx

View workflow job for this annotation

GitHub Actions / Static Analysis and Test | Node 18

Forbidden non-null assertion

Check failure on line 91 in src/components/share-button.tsx

View workflow job for this annotation

GitHub Actions / Static Analysis and Test | Node 20

Forbidden non-null assertion
target="_blank"
rel="noopener noreferrer"
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 1,
border: '1px solid rgba(255,255,255,0.12)',
p: 1,
width: 40,
height: 40,
minWidth: 40,
}}
>
<Iconify icon={icon} width={20} />
</Button>
),
)}

<Button
ref={navRefSocial}
onMouseEnter={handleOpenShare}
onMouseLeave={handleCloseShare}
onClick={handlePopoverOpen}
size="medium"
variant={buttonVariant}
sx={{ p: 1, minWidth: 44, ...buttonSx }}
>
<Iconify icon="ion:share-social-outline" width={20} />
</Button>
<Popover
open={openTooltipShare}
anchorEl={navRefSocial.current}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'center' }}
slotProps={{
paper: {
onMouseEnter: handleOpenShare,
onMouseLeave: handleCloseShare,
sx: {
mt: 6,
backgroundColor: 'rgba(0,0,0,0.6)',
p: '8px 20px',
...(openPopover && { pointerEvents: 'auto' }),
},
},
}}
sx={{ pointerEvents: 'none' }}
>
<Typography>Share</Typography>
</Popover>

<Popover
open={openPopover}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
PaperProps={{
sx: {
background: 'linear-gradient(90deg,#1C1C1E,#2C2C2E)',
borderRadius: 2,
p: 2,
display: 'flex',
flexDirection: 'column',
gap: 1,
mt: 1.5,
alignItems: 'center',
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
},
}}
>
<Typography variant="body1" fontWeight="bold" align="center" gutterBottom>
Share link to this page
</Typography>

<Stack direction="row" spacing={2} justifyContent="center">
{shareLinks.map((item) => (
<Stack key={item.label} direction="column" alignItems="center">
<Button
component="a"
href={replacePlaceholder(item.url)}
target="_blank"
rel="noopener noreferrer"
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 1,
border: '1px solid rgba(255,255,255,0.12)',
p: 1,
mb: 2,
width: 40,
height: 40,
minWidth: 40,
}}
>
<Iconify icon={item.icon} width={20} />
</Button>
<Typography variant="subtitle2" fontSize={10} color="text.secondary">
{item.label}
</Typography>
</Stack>
))}

<Stack direction="column" alignItems="center">
<Button
onClick={handleCopy}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 1,
border: '1px solid rgba(255,255,255,0.12)',
p: 1,
mb: 2,
width: 40,
height: 40,
minWidth: 40,
}}
>
<Iconify icon="mdi:link-variant" width={20} />
</Button>
<Typography variant="subtitle2" fontSize={10} color="text.secondary">
Copy
</Typography>
</Stack>
</Stack>
</Popover>
</>
);
};

export default ShareButton;
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
performance.mark('entry:begin');

import './init.js';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
Expand Down
31 changes: 31 additions & 0 deletions src/sections/publication/CONSTANTS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,34 @@ export const MAX_LINES = 5;
export const PUBLICATION_NEW_WIZARD_STEPS = ['Movie Information', 'Media Assets & Technical Details', 'Distribution & Rights'];

export const PUBLICATION_DESCRIPTION_MAX_LINES = 4;

export const urlToShare = 'https://app.watchit.movie/publicationId';

export const shareLinks = [
{
icon: 'mingcute:social-x-line',
label: 'X',
url: `https://x.com/share/?url=${encodeURIComponent(
urlToShare,
)}&text=Watch%20this%20movie%20on%20Watchit&hashtags=Watchit,Movie,Streaming`,
},
{
icon: 'mdi:facebook',
label: 'Facebook',
url: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(urlToShare)}`,
},
{
icon: 'mdi:telegram',
label: 'Telegram',
url: `https://telegram.me/share/?url=${encodeURIComponent(
urlToShare,
)}&text=Watch%20this%20movie%20on%20Watchit`,
},
];

export const socialMedia = [
{ key: 'twitter', icon: 'mingcute:social-x-line' },
{ key: 'instagram', icon: 'mdi:instagram' },
{ key: 'orb', icon: 'mdi:instagram' },
{ key: 'farcaster', icon: 'mdi:instagram' },
];
27 changes: 27 additions & 0 deletions src/sections/publication/components/publication-share.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC } from 'react';
import ShareButton from '@src/components/share-button';
import { shareLinks, urlToShare } from '@src/sections/publication/CONSTANTS';
import { Post } from '@src/graphql/generated/graphql.ts';

interface Props {
post: Post;
}

const PublicationShare: FC<Props> = ({ post }) => (
<ShareButton
placeholder="publicationId"
pathPrefix="publication/"
targetId={post.id}
shareLinks={shareLinks}
templateUrl={urlToShare}
buttonVariant="text"
buttonSx={{
borderColor: '#FFFFFF',
color: '#FFFFFF',
height: 40,
minWidth: 40,
}}
/>
);

export default PublicationShare;
Loading
Loading