Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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 apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AuthProvider } from '@components/AuthProvider';
import { ProtectedRoute } from '@components/ProtectedRoute';
import { LoginPage } from '@containers/auth/LoginPage';
import { DashboardPage } from '@containers/dashboard/DashboardPage';
import ShareOptions from '@containers/donations/ShareOptions';

const router = createBrowserRouter([
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ShareOptions from '@containers/donations/ShareOptions';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

export interface CarouselSlide {
Expand Down Expand Up @@ -196,6 +197,7 @@ export const AutoRotatingTestimonialCarousel: React.FC<Props> = ({
/>
</div>
</div>
<ShareOptions activeSlideUrl={slides[activeIndex].image} />
</div>
);
};
Expand Down
144 changes: 144 additions & 0 deletions apps/frontend/src/containers/donations/ShareOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Button } from '@components/ui/button';
import React, { useState } from 'react';
import {
FacebookShareButton,
FacebookIcon,
XIcon,
TwitterShareButton,
LinkedinShareButton,
LinkedinIcon,
} from 'react-share';

const ShareOptions = ({ activeSlideUrl }: { activeSlideUrl: string }) => {
const [isCopyingText, setIsCopyingText] = useState(false);
const [isCopyingImage, setIsCopyingImage] = useState(false);
const message = `Want to support your community? Join me in donating to the Fenway Community Center!\n\n${window.location.href}`;

const handleCopyTextClick = async () => {
try {
setIsCopyingText(true);
await navigator.clipboard.writeText(message);
setTimeout(() => setIsCopyingText(false), 1000);
} catch (err) {
console.error('Failed to copy message to clipboard');
alert('Failed to copy message to clipboard');
}
};

const handleCopyImageClick = async () => {
try {
const response = await fetch(activeSlideUrl);
if (!response.ok) {
throw new Error('Failed to fetch image');
}

const blob = await response.blob();
const imageType = blob.type || 'image/png';
const imageItem = new ClipboardItem({ [imageType]: blob });
await navigator.clipboard.write([imageItem]);

setIsCopyingImage(true);
setTimeout(() => setIsCopyingImage(false), 1000);
} catch (err) {
console.error('Failed to copy image to clipboard', err);
alert('Failed to copy image to clipboard');
}
};

return (
<div>
<div
className="flex flex-wrap justify-center gap-3"
style={{ marginTop: '1.5rem' }}
>
<Button
variant="unstyled"
size="sm"
style={{
backgroundColor: '#007b64',
color: 'white',
padding: '1.5rem',
fontWeight: 'bold',
}}
className="w-[13rem] gap-3 rounded-[10px] min-h-[2.5rem] flex justify-center items-center text-center text-[1.3rem] hover:opacity-90 transition-opacity"
onClick={handleCopyTextClick}
>
{isCopyingText ? 'Copied!' : 'Copy message'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
stroke="#FFFFFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ marginLeft: '10px' }}
>
<path
fill="none"
d="M12 2v13m4-9l-4-4l-4 4m-4 6v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"
/>
</svg>
</Button>
<Button
Comment thread
aaronashby marked this conversation as resolved.
Outdated
variant="unstyled"
size="sm"
style={{
backgroundColor: '#007b64',
color: 'white',
padding: '1.5rem',
fontWeight: 'bold',
}}
className="w-[13rem] gap-3 rounded-[10px] min-h-[2.5rem] flex justify-center items-center text-center text-[1.3rem] hover:opacity-90 transition-opacity"
onClick={handleCopyImageClick}
>
{isCopyingImage ? 'Copied!' : 'Copy image'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
stroke="#FFFFFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ marginLeft: '10px' }}
>
<path
fill="none"
d="M12 2v13m4-9l-4-4l-4 4m-4 6v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"
/>
</svg>
</Button>
Comment thread
aaronashby marked this conversation as resolved.
Outdated
</div>
<div
className="flex justify-center"
style={{ gap: '3rem', marginTop: '2rem' }}
>
<FacebookShareButton url={window.location.href}>
<FacebookIcon
className="rounded-full"
style={{ maxWidth: '2.5rem', height: 'auto' }}
/>
</FacebookShareButton>
<TwitterShareButton url={window.location.href}>
<XIcon
className="rounded-full"
style={{ maxWidth: '2.5rem', height: 'auto' }}
/>
</TwitterShareButton>
<LinkedinShareButton url={window.location.href}>
<LinkedinIcon
className="rounded-full"
style={{ maxWidth: '2.5rem', height: 'auto' }}
/>
</LinkedinShareButton>
</div>
</div>
);
};

export default ShareOptions;
1 change: 1 addition & 0 deletions apps/frontend/src/containers/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DonationForm } from './donations/DonationForm';
import CarouselImage1 from '@components/testimonials/TestimonialImages/Carousel_image1.png';
import CarouselImage2 from '@components/testimonials/TestimonialImages/Carousel_image2.png';
import CarouselImage3 from '@components/testimonials/TestimonialImages/Carousel_image3.png';
import ShareOptions from './donations/ShareOptions';

const SAMPLE_DONATION: SampleDonation = {
name: 'C4C',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"react-share": "^5.2.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"sqlite3": "^5.1.7",
Expand Down
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7277,6 +7277,11 @@ class-variance-authority@^0.7.1:
dependencies:
clsx "^2.1.1"

classnames@^2.3.2:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==

clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz"
Expand Down Expand Up @@ -8030,7 +8035,7 @@ dayjs@^1.10.4:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz"
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==

debug@2.6.9:
debug@2.6.9, debug@^2.1.3:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
Expand Down Expand Up @@ -11724,6 +11729,13 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"

jsonp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/jsonp/-/jsonp-0.2.1.tgz#a65b4fa0f10bda719a05441ea7b94c55f3e15bae"
integrity sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==
dependencies:
debug "^2.1.3"

jsonwebtoken@^9.0.0:
version "9.0.2"
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
Expand Down Expand Up @@ -14574,6 +14586,14 @@ react-router@6.17.0:
dependencies:
"@remix-run/router" "1.10.0"

react-share@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/react-share/-/react-share-5.2.2.tgz#bd011e9c53a6adf97ef75c062a2e8b24744c4de2"
integrity sha512-z0nbOX6X6vHHWAvXduNkYeJUKTKNpKM5Xpmc5a2BxjJhUWl+sE7AsSEMmYEUj2DuDjZr5m7KFIGF0sQPKcUN6w==
dependencies:
classnames "^2.3.2"
jsonp "^0.2.1"

react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz"
Expand Down