Skip to content

Commit 4455ac8

Browse files
committed
add share fallback ui
1 parent 594ee62 commit 4455ac8

File tree

10 files changed

+621
-10
lines changed

10 files changed

+621
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@zeit/next-css": "0.2.1-canary.4",
2929
"airtable": "^0.7.0",
3030
"clsx": "^1.0.4",
31+
"copee": "^1.0.6",
3132
"cuid": "^2.1.6",
3233
"fromnow": "^3.0.1",
3334
"is-mobile": "^2.0.1",

src/components/CrowdFund/list.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useContext } from 'react';
22
import Link from 'next/link';
33
import useSWR from 'swr';
44

@@ -8,8 +8,10 @@ import { CampaignProgress } from './progress';
88
import { Campaign } from '../../services/airtable';
99
import { CampaignEmptyState } from '../empty-states/campaign';
1010
import fetch from '../../lib/fetch';
11+
import { ShareContext } from '../../services/share';
1112

1213
export const CampaignList = () => {
14+
const { isOpen, openShareDialog } = useContext(ShareContext);
1315
const { data, error } = useSWR<Campaign[]>('/api/campaigns', fetch);
1416

1517
if (error) {
@@ -44,10 +46,11 @@ export const CampaignList = () => {
4446
<CampaignProgress campaign={item} />
4547
<div className="flex justify-between items-center">
4648
<button
49+
disabled={isOpen}
4750
onClick={e => {
4851
e.stopPropagation();
4952
e.preventDefault();
50-
alert('Hey');
53+
openShareDialog();
5154
}}
5255
className="p-4 text-center">
5356
<ShareIcon />
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import React, { useContext, useEffect, useState } from 'react';
2+
import { toClipboard } from 'copee';
3+
4+
import { ShareContext } from '../../services/share';
5+
import { WhatsappIcon, FacebookIcon, MessangerIcon, TwitterIcon, CopyIcon } from '../Icons/Share';
6+
7+
export function FallbackShare() {
8+
const { closeShareDialog, isOpen } = useContext(ShareContext);
9+
const [isMounted, setIsMounted] = useState(false);
10+
useEffect(() => {
11+
setIsMounted(true);
12+
}, []);
13+
14+
if (!isMounted) {
15+
return null;
16+
}
17+
18+
const title = document.title;
19+
const url = window.location.href;
20+
21+
const whatsappText = `${title}\n\n${url}`;
22+
function copy() {
23+
const success = toClipboard(url);
24+
if (success) {
25+
alert(`Successfully copied link to your clipboard`);
26+
} else {
27+
alert(`Failed to copied link to your clipboard`);
28+
}
29+
}
30+
return (
31+
<div className="container" role="button" onClick={closeShareDialog}>
32+
<div className="popup shadow-2xl bg-gray-100 border border-gray-200">
33+
<h3>Share via</h3>
34+
<ul>
35+
<li>
36+
<a href={`whatsapp://send?text=${encodeURIComponent(whatsappText)}`}>
37+
<WhatsappIcon style={{ width: 50, height: 50 }} />
38+
<div className="icon-name">WhatsApp</div>
39+
</a>
40+
</li>
41+
<li>
42+
<a
43+
href={`http://www.facebook.com/sharer.php?u=${encodeURIComponent(url)}&p[title]=${encodeURIComponent(
44+
title
45+
)}`}>
46+
<FacebookIcon style={{ width: 40, height: 40 }} />
47+
<div style={{ position: 'relative', top: 5 }} className="icon-name">
48+
Facebook
49+
</div>
50+
</a>
51+
</li>
52+
<li>
53+
<a href={`fb-messenger://share/?link=${encodeURIComponent(url)}`}>
54+
<MessangerIcon style={{ width: 45, height: 45 }} />
55+
<div style={{ position: 'relative', top: 2 }} className="icon-name">
56+
Messanger
57+
</div>
58+
</a>
59+
</li>
60+
<li>
61+
<a
62+
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(
63+
url
64+
)}`}>
65+
<TwitterIcon style={{ width: 40, height: 40 }} />
66+
<div style={{ position: 'relative', top: 0 }} className="icon-name">
67+
Twitter
68+
</div>
69+
</a>
70+
</li>
71+
<li>
72+
<button type="button" onClick={copy} className="social-link btn">
73+
<CopyIcon style={{ width: 36, height: 36 }} />
74+
<div className="icon-name">Copy to clipboard</div>
75+
</button>
76+
</li>
77+
</ul>
78+
</div>
79+
<style jsx>
80+
{`
81+
.container {
82+
width: 100%;
83+
height: 100vh;
84+
position: absolute;
85+
top: 0;
86+
left: 0;
87+
right: 0;
88+
bottom: 0;
89+
z-index: ${isOpen ? 1000 : -1};
90+
}
91+
.popup {
92+
min-height: 240px;
93+
bottom: 80px;
94+
position: absolute;
95+
z-index: 2;
96+
padding: 12px 4px;
97+
transition: all 0.25s cubic-bezier(0.4, 0, 0.6, 1);
98+
transform: ${isOpen ? `translateY(0)` : `translateY(200%)`};
99+
opacity: ${isOpen ? 1 : 0};
100+
margin: 4px;
101+
left: 10px;
102+
right: 10px;
103+
border-radius: 16px;
104+
z-index: ${isOpen ? 'auto' : -1};
105+
}
106+
h3 {
107+
text-align: center;
108+
}
109+
ul {
110+
display: flex;
111+
flex-wrap: wrap;
112+
padding: 0;
113+
margin: 0;
114+
list-style: none;
115+
align-items: center;
116+
}
117+
ul li {
118+
margin: 24px;
119+
}
120+
ul li :global(.social-link) {
121+
display: block;
122+
}
123+
ul li :global(.social-link.btn) {
124+
-webkit-tap-highlight-color: transparent;
125+
appearence: none;
126+
outline: none;
127+
border: none;
128+
background: transparent;
129+
}
130+
.icon-name {
131+
width: 40px;
132+
font-size: 10px;
133+
display: flex;
134+
align-items: center;
135+
text-align: center;
136+
justify-content: center;
137+
margin: 0 auto;
138+
color: #888;
139+
}
140+
`}
141+
</style>
142+
</div>
143+
);
144+
}

src/components/Icons/Share.tsx

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.

src/components/common/MobileMenu.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import React from 'react';
1+
import React, { useContext } from 'react';
22

33
import { ActiveLink } from './ActiveLink';
44
import { AboutIcon, CrowdFundIcon, HelpIcon, ShareIcon } from '../Icons/common';
5+
import { ShareContext } from '../../services/share';
56

67
export const MobileMenu = () => {
8+
const { openShareDialog, isOpen } = useContext(ShareContext);
79
return (
810
<div className="fixed bottom-0 left-0 w-full h-16 bg-gray-100 shadow-xl z-10 md:hidden">
911
<div className="shadow-xl flex items-center w-full h-16 text-gray-600">
@@ -25,7 +27,10 @@ export const MobileMenu = () => {
2527
<span className="text-sm mt-1">Help</span>
2628
</a>
2729
</ActiveLink>
28-
<button className="px-6 text-center flex flex-col items-center flex-1">
30+
<button
31+
onClick={openShareDialog}
32+
disabled={isOpen}
33+
className="px-6 text-center flex flex-col items-center flex-1">
2934
<ShareIcon />
3035
<span className="text-sm mt-1">Share</span>
3136
</button>

src/pages/_app.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import '../styles/index.css';
22

3-
import React, { FunctionComponent } from 'react';
3+
import React, { FunctionComponent, useState } from 'react';
44
import { AppProps } from 'next/app';
55
import Div100vh from 'react-div-100vh';
66
import NProgress from 'nprogress';
77
import Router from 'next/router';
88
import Head from 'next/head';
99

1010
import { MobileMenu } from '../components/common/MobileMenu';
11+
import { FallbackShare } from '../components/FallbackShare';
12+
import { ShareContext } from '../services/share';
1113

1214
Router.events.on('routeChangeStart', url => {
1315
console.log(`Loading: ${url}`);
@@ -17,16 +19,34 @@ Router.events.on('routeChangeComplete', () => NProgress.done());
1719
Router.events.on('routeChangeError', () => NProgress.done());
1820

1921
const App: FunctionComponent<AppProps> = ({ Component, pageProps }) => {
22+
const [showFallbackShare, setShowFallbackShare] = useState(false);
23+
function openShareDialog() {
24+
// try to open native share dialog
25+
if (window.navigator.share) {
26+
const title = document.title;
27+
const url = window.location.href;
28+
const text = document.querySelector("meta[property='description']").getAttribute('content');
29+
return window.navigator.share({ title, text: `${text} ${url}` });
30+
}
31+
// fallback to custom native share dialog
32+
return setShowFallbackShare(true);
33+
}
34+
function closeShareDialog() {
35+
setShowFallbackShare(false);
36+
}
2037
return (
2138
<>
2239
<Head>
2340
{/* Import CSS for nprogress */}
2441
<link rel="stylesheet" type="text/css" href="/nprogress.css" />
2542
</Head>
26-
<Div100vh>
27-
<Component {...pageProps} />
28-
<MobileMenu />
29-
</Div100vh>
43+
<ShareContext.Provider value={{ openShareDialog, closeShareDialog, isOpen: showFallbackShare }}>
44+
<Div100vh className="relative">
45+
<Component {...pageProps} />
46+
<FallbackShare />
47+
<MobileMenu />
48+
</Div100vh>
49+
</ShareContext.Provider>
3050
</>
3151
);
3252
};

src/pages/crowdfund/[slug].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const CampaignPage: NextPage = ({ initialData, slug }) => {
4444
)}
4545
</div>
4646

47-
<div className="mt-6 md:mt-0 md:mx-2 md:w-1/3 md:bg-white md:p-4 md:shadow md:rounded-lg">
47+
<div className="mt-10 md:mt-0 md:mx-2 md:w-1/3 md:bg-white md:p-4 md:shadow md:rounded-lg">
4848
<h2 className="text-xl mb-1 font-medium text-gray-800">Recent Contributions</h2>
4949
<p className="text-sm text-gray-700">A special thanks to all who raised the funds for this campaign.</p>
5050
<DonorsList campaign={data} />

src/services/share.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
export const ShareContext = React.createContext({
4+
isOpen: false,
5+
openShareDialog: () => null,
6+
closeShareDialog: () => null,
7+
});

types/global.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ declare global {
1010
interface Window {
1111
Razorpay: typeof Razorpay;
1212
}
13+
14+
interface Navigator {
15+
share: (args: { title: string; text: string }) => void;
16+
}
1317
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2314,6 +2314,11 @@ cookie@0.4.0:
23142314
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
23152315
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
23162316

2317+
copee@^1.0.6:
2318+
version "1.0.6"
2319+
resolved "https://registry.yarnpkg.com/copee/-/copee-1.0.6.tgz#82b1b348b7df9dac1a5298dfdfa5063ae7dce6c3"
2320+
integrity sha512-l5mIlUejgG4Q+pb8yoDPDFI6ehjx4u6vaUd2tv4HxaaZ3vMkuQmmFStU1BNmomj3DXtht6mjD4ZiUOVu72v/QA==
2321+
23172322
copy-concurrently@^1.0.0:
23182323
version "1.0.5"
23192324
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"

0 commit comments

Comments
 (0)