├── .gitignore
├── FAQs.js
├── README.md
├── about.js
├── components
├── DarkMode.js
├── Form.js
├── HomePage.js
├── Layout.js
└── MultiCards.js
├── context
└── CountryContext.js
├── next.config.js
├── package.json
├── pages
├── _app.js
├── _document.js
├── guides
│ ├── [slug].js
│ └── index.js
└── index.js
├── pnpm-lock.yaml
├── posthog.js
├── public
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── icons
│ ├── double-tick.svg
│ ├── icon-sq.svg
│ ├── icon.svg
│ ├── moon.png
│ └── sun.png
├── manifest.json
├── meta-image-01.png
├── meta-image-02.png
└── meta-image-03.png
├── screenshot-iphonex.png
├── screenshot.png
├── service-worker.js
├── styles
└── global.css
└── utils
└── graphcms.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out
3 |
4 | .next
5 | .netlify
6 | .DS_Store
7 | .env.local
--------------------------------------------------------------------------------
/FAQs.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export const faqs = [
4 | {
5 | q: <>How to WhatsApp without saving number?>,
6 | a:
7 | To WhatsApp without saving the number, follow these steps
8 | Open this tool
9 | Enter the recipient's contact number
10 | Tap "Open WhatsApp Chat" button
11 | Send WhatsApp as you normally do
12 |
13 | },
14 | {
15 | q: <>How to make a call on WhatsApp without saving contact?>,
16 | a: <>
17 | Using this tool, you will be able to open WhatsApp chat window without saving number. You will be able to call the recipient from WA chat screen.
18 |
19 | Follow these steps to call unknown number -
20 | Open this tool
21 | Enter the recipient's contact number
22 | Tap "Open WhatsApp Chat" button
23 | Call on WhatsApp as you normally do
24 |
25 | >
26 | },
27 | {
28 | q: <>What if this page saves my contacts?>,
29 | a: <>
30 | Thanks for asking. While there is no perfect way for any creator to assure their users that their service is 100% secure, I will try my best to answer your concern..
31 | No, this tool does not save any personal data.
32 | The only thing that the web app collects is analytics data (page visits, clicks) via Google Analytics and PostHog. Since contact number is not appended to URL before redirecting, analytics wouldn't collect number with url.So no personally identifiable, private data is shared or saved.
33 |
34 | How do you assure your user that you don't save contacts?
35 | Again, I wish there was a way I could give full assurance that I don't collect private data. Except for checking the web app security by yourself, every time before entering the contact number, you will have to take my word on this and trust me.
36 | As a step towards being open, I have open-sourced code on github .
37 | But then, the code deployed on the website can be something else. You will either have to trust this web app (or any website on the internet for this matter) or build yourself from scratch.
38 | At last, I can hire a cybersecurity agency to audit the website, but then you will have to trust the agency :D
39 | >
40 | },
41 | {
42 | q: <>How does this tool work?>,
43 | a: <>
44 | WhatsApp has an unpopular way to open chat conversation with unsaved contact number - wa.me link.
45 | You can create a special wa.me link (https://wa.me/COUNTRYCODE+PHONE) and open it in browser.
46 | WhatsApp Anyone use the same mechanism, but while offering you added convenience! It's simple, secure & installable website.
47 | >
48 | },
49 | {
50 | q: <>Does the tool support WhatsApp Web?>,
51 | a: <>Yes, WhatsApp Anyone also supports WhatsApp Web to send message or call unsaved number. Based on whether WhatsApp app is installed, you will either be redirected to WA app or WA web.>
52 | },
53 | {
54 | q: <>Do you have any how-to guides?>,
55 | a: <>Read our how-to guides to increase WhatsApp productivity>
56 | },
57 | {
58 | q: <>Does WhatsApp Anyone have Android and iOS app?>,
59 | a: <>WhatsApp Anyone is a Progressive Web App that can be installed on your home screen. Most browsers, and operating systems are supported - Android, iOS, Windows, MacOS, Linux. Add to Home Screen >
60 | },
61 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WhatsApp Anyone
2 |
3 | WhatsApp Anyone is a tool to start WhatsApp chat with an unknown number, without need to save their contact.
4 |
5 | > Live @ https://whatsapp-anyone.pgxplorer.dev
6 |
7 | [ ](https://whatsapp-anyone.pgxplorer.dev)
8 |
9 | ### Top features
10 | - Add to Home Screen (for quick access)
11 | - Secure (your personal data is not saved)
12 | - Minimalist UI (to get things done)
13 | - Country drop down for fast interaction
14 | - Dark & light theme
15 |
16 |
17 | ### Tech
18 | * [next.js](nextjs.org) (a [react.js](reactjs.org) framework) - for server side rendering
19 | * [next-offline](https://github.com/hanford/next-offline) - for PWA
20 | * vanilla HTML & CSS
21 | * [React-Phone-Input-2](https://www.npmjs.com/package/react-phone-input-2)
22 |
23 |
24 | ### Installation
25 | This project requires [Node.js](https://nodejs.org/) to run.
26 |
27 | Install the dependencies and devDependencies and start the server.
28 |
29 | ```sh
30 | $ git clone https://github.com/cTxplorer/whatsapp-anyone.git
31 | $ cd whatsapp-anyone
32 | $ npm install
33 | $ npm run dev
34 | ```
35 |
36 | ### Development
37 |
38 | Want to contribute? Great!
39 |
40 | NextJs provides hot reloading capablity - make a change in your file and instantaneously see your updates!
41 |
42 | Open your favorite Terminal and run this command -
43 |
44 | ```sh
45 | $ npm run dev
46 | ```
47 |
48 | Open your favorite editor and start contributing!
49 |
50 | ### Todos
51 |
52 | - language localization
53 | - Improve styling on larger screens
54 | - add code comments
55 | - add contributing.md file
56 |
57 |
58 | ### License
59 | ----
60 | MIT
61 |
--------------------------------------------------------------------------------
/about.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout from '../components/Layout';
3 |
4 | const About = () => (
5 | <>
6 |
7 | About | Whatsapp unsaved contact
8 |
9 |
10 | About us
11 | Oh, hi. This is a side project built out of personal need
12 |
13 | >
14 | );
15 |
16 | export default About;
--------------------------------------------------------------------------------
/components/DarkMode.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { posthog } from 'posthog-js'
3 |
4 | const setDarkMode = (active = false) => {
5 | const wrapper = document.querySelector(":root");
6 | const modeName = active ? "dark" : "light";
7 | wrapper.setAttribute("data-theme", modeName);
8 | localStorage.setItem("theme", modeName);
9 | };
10 |
11 | const toggleDarkMode = () => {
12 | const theme = document.querySelector(":root").getAttribute("data-theme");
13 | setDarkMode(theme === "light");
14 | posthog.capture('WA - Toggled Dark Theme', { isDark: theme === "light"});
15 | };
16 |
17 | const toggleOnMatchMediaChange = e => {
18 | setDarkMode(e.matches);
19 | posthog.capture('WA - Toggled Dark Theme On System Change', { isDark: theme === "light"});
20 | }
21 |
22 | const initDarkMode = () => {
23 | const themePreference = localStorage.getItem("theme");
24 | const query = window.matchMedia("(prefers-color-scheme: dark)");
25 | setDarkMode(themePreference ? themePreference === "dark" : query.matches);
26 | query.addEventListener("change", toggleOnMatchMediaChange);
27 | return query;
28 | };
29 |
30 |
31 | const DarkMode = ({ children }) => {
32 | useEffect(() => {
33 | const query = initDarkMode();
34 | return () => {
35 | query.removeEventListener("change", toggleOnMatchMediaChange);
36 | }
37 | }, []);
38 | return (
39 | <>
40 |
41 |
42 |
43 |
44 | {children}
45 | >
46 | );
47 | }
48 |
49 | export default DarkMode;
--------------------------------------------------------------------------------
/components/Form.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PhoneInput from 'react-phone-input-2';
3 | import { posthog } from 'posthog-js';
4 | import { isPossibleNumber } from 'libphonenumber-js';
5 |
6 | class Form extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | contactVal: '',
11 | contact: '',
12 | messageText: 'Hi!',
13 | showCCPrompt: false,
14 | }
15 | }
16 |
17 | onPhoneChange = (value, country) => {
18 | let isValid = false // Basic validation
19 | if (country.countryCode) {
20 | localStorage.setItem("lastSelectedCountryIso2", country.countryCode);
21 | isValid = isPossibleNumber(value, country.countryCode?.toUpperCase());
22 | }
23 | this.setState({
24 | contactVal: value,
25 | contact: value,
26 | isValid,
27 | showCCPrompt: false,
28 | });
29 | this.forceUpdate();
30 | }
31 |
32 | handleSubmit = (ev) => {
33 | ev.preventDefault();
34 |
35 | if (this.state.isValid) {
36 | const fullContact = this.state.contact;
37 | const encodedMsg = encodeURIComponent(this.state.messageText);
38 | const waLink = `https://wa.me/${fullContact}?text=${encodedMsg}`;
39 | posthog.capture('WA - Opened WA chat');
40 | window.open(waLink, "_blank");
41 | } else {
42 | document
43 | .getElementsByClassName('wa-form')[0]
44 | .getElementsByTagName('input')[0]
45 | .focus();
46 | posthog.capture('WA - Viewed Phone Input Error');
47 | this.setState({ showCCPrompt: true });
48 | }
49 | }
50 |
51 | render() {
52 | const { country } = this.props;
53 | return (
54 |
82 | )
83 | }
84 | }
85 |
86 | export default Form;
--------------------------------------------------------------------------------
/components/HomePage.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useContext } from 'react';
3 | import { CountryContext } from '../context/CountryContext';
4 | import Layout from './Layout';
5 | import Form from './Form';
6 | import { faqs } from '../FAQs';
7 | import MultiCards from './MultiCards';
8 |
9 | // import styles from './HomePage.css';
10 |
11 | const HomePage = () => {
12 | const [showA2HS, updateShowA2HS] = useState(false);
13 | const [deferredPrompt, updatedDeferredPrompt] = useState(null);
14 | const country = useContext(CountryContext);
15 |
16 | useEffect(() => {
17 | window.addEventListener('beforeinstallprompt', (e) => {
18 | e.preventDefault();
19 | updateShowA2HS(true);
20 | updatedDeferredPrompt(e);
21 | });
22 | }, []);
23 |
24 | const onA2hsClickHandler = () => {
25 | if (deferredPrompt) {
26 | deferredPrompt.prompt();
27 | deferredPrompt.userChoice.then((choiceResult) => {
28 | if (choiceResult.outcome === 'accepted') {
29 | updateShowA2HS(false);
30 | }
31 | });
32 | }
33 | }
34 |
35 | return (
36 | <>
37 |
38 |
39 |
40 |
41 |
42 | WhatsApp Anyone
43 |
44 |
47 |
70 |
71 |
72 |
73 |
74 |
75 | Simple Tool, Many Use-cases
76 | How is this tool helping you? Please share your use-case , and we will feature it! !>
92 | }
93 | ]} />
94 |
95 |
96 |
97 |
98 |
99 | Questions & Answers
100 |
101 | {faqs.map(faq => (
102 |
103 | {faq.q}
104 | {faq.a}
105 |
106 | ))}
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | User Reviews
115 | “>,
118 | content: <>Great work, thank you for sharing! I added the shortcut to my home screen so it is easy to use whenever. Thanks a lot!>,
119 | },
120 | {
121 | title: <>“>,
122 | content: <>Fantastic project. Really hate to save someone’s number just to text them on WhatsApp. Love the source code too, very easy to read.>
123 | }
124 | ]} />
125 |
126 |
127 |
128 |
129 |
130 |
131 | WARNING: WhatsApp Anyone is NOT for those..
132 |
150 |
151 |
152 |
153 |
154 |
162 |
163 | >
164 | );
165 | }
166 |
167 | export default HomePage;
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 |
3 | const Layout = (props) => (
4 | <>
5 |
6 | WhatsApp without saving number - WhatsApp Anyone app
7 |
8 |
9 |
10 | {/* */}
11 |
12 | {/* Global site tag (gtag.js) - Google Analytics */}
13 |
14 |
23 |
24 | {/* Google Tag Manager */}
25 |
30 |
31 |
32 | {/* Google Tag Manager (noscript) */}
33 | `
36 | }}
37 | />
38 |
39 | {props.children}
40 | >
41 | );
42 |
43 | export default Layout;
--------------------------------------------------------------------------------
/components/MultiCards.js:
--------------------------------------------------------------------------------
1 | const Card = ({ title, content }) => (
2 |
3 |
{title}
4 |
{content}
5 |
6 | );
7 |
8 | const MultiCards = ({ content }) => {
9 | return (
10 |
11 | {
12 | content.map(({ title, content }) => (
13 |
14 | ))
15 | }
16 |
17 | );
18 | }
19 |
20 | export default MultiCards;
--------------------------------------------------------------------------------
/context/CountryContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | export const CountryContext = createContext();
4 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withOffline = require('next-offline')
2 |
3 | module.exports = withOffline({
4 | output: 'export',
5 | // Use the default configuration
6 | // The service worker will be generated in public/service-worker.js
7 | })
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wa-unknown",
3 | "version": "2.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "libphonenumber-js": "^1.12.7",
16 | "next": "^14.1.0",
17 | "next-offline": "^5.0.4",
18 | "posthog-js": "^1.236.2",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-phone-input-2": "^2.15.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 | import 'react-phone-input-2/lib/bootstrap.css'
3 | import posthog from "posthog-js";
4 | import { PostHogProvider } from 'posthog-js/react';
5 | import DarkMode from '../components/DarkMode';
6 |
7 | if (typeof window !== 'undefined') { // checks that we are client-side
8 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
9 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
10 | loaded: (posthog) => {
11 | if (process.env.NODE_ENV === 'development') posthog.debug() // debug mode in development
12 | },
13 | })
14 | }
15 |
16 | export default function App({ Component, pageProps }) {
17 | return
18 | }
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Document, { Html, Head, Main, NextScript } from 'next/document';
3 |
4 | //server side rendering for material-ui https://material-ui.com/guides/server-rendering/
5 | export default class MyDocument extends Document {
6 | render() {
7 | const appName = "WhatsApp Anyone";
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {process.env.NODE_ENV !== 'production' && }
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/pages/guides/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import ErrorPage from "next/error";
4 | import { getPostBySlug, getAllPosts } from '../../utils/graphcms';
5 |
6 | export default function Post({ post, allPosts }) {
7 | if (!post?.slug) {
8 | return ;
9 | }
10 |
11 | const title = `${ post?.seoOverride?.title || post.title } - WhatsApp Anyone`;
12 | const description = post?.seoOverride?.description || post.excerpt;
13 | const websiteUrl = `https://whatsapp-anyone.pgxplorer.dev/guides/${ post.slug }`;
14 |
15 | return (
16 | <>
17 |
18 |
19 |
20 | {title}
21 |
22 | {/* Primary Meta Tags */}
23 |
24 |
25 |
26 | {/* Open Graph / Facebook */}
27 |
28 |
29 |
30 |
31 |
32 |
33 | {/* Twitter */}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | WhatsApp Anyone
44 |
45 |
46 | {post.title}
47 |
48 | {post.coverImage ?
: ''}
49 |
50 | {post.content ? (
51 |
52 | ) : ''}
53 |
54 |
55 |
56 | Liked this guide? Check more how-to guides to increase WhatsApp productivity
57 |
58 | {allPosts?.length ?
59 | All guides:
60 |
61 | {
62 | allPosts.map((post) => (
63 |
64 | {post.title}
65 | {post.excerpt}
66 |
67 | ))
68 | }
69 |
70 |
: ''}
71 |
72 | >
73 | );
74 | }
75 |
76 | export async function getStaticProps({ params }) {
77 | const [post, allPosts] = await Promise.all([getPostBySlug(params.slug), getAllPosts()]);
78 | return {
79 | props: {
80 | post,
81 | allPosts: allPosts.filter(post => post.slug !== params.slug),
82 | },
83 | };
84 | }
85 |
86 | export async function getStaticPaths() {
87 | const posts = await getAllPosts();
88 | return {
89 | paths: posts.map(({ slug }) => ({
90 | params: { slug },
91 | })),
92 | fallback: false,
93 | };
94 | }
95 |
--------------------------------------------------------------------------------
/pages/guides/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import { getAllPosts } from '../../utils/graphcms';
4 |
5 | const title = `How-To Guides - WhatsApp Anyone`;
6 | const description = "Increase your WhatsApp productivity with our how-to guides.";
7 | const websiteUrl = `https://whatsapp-anyone.pgxplorer.dev/guides`;
8 |
9 |
10 | export default function Post({ posts = [] }) {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {title}
17 |
18 | {/* Primary Meta Tags */}
19 |
20 |
21 |
22 | {/* Open Graph / Facebook */}
23 |
24 |
25 |
26 |
27 |
28 |
29 | {/* Twitter */}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | WhatsApp Anyone
39 |
40 |
41 |
42 | Our how-to guides on using WhatsApp
43 |
44 |
45 | {
46 | posts.map((post) => (
47 |
48 | {post.title}
49 | {post.excerpt}
50 |
51 | ))
52 | }
53 |
54 |
55 |
56 | >
57 | );
58 | }
59 |
60 | export async function getStaticProps() {
61 | const posts = await getAllPosts();
62 | console.log(posts);
63 | return {
64 | props: {
65 | posts,
66 | },
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useEffect, useState } from 'react';
3 | import HomePage from '../components/HomePage';
4 | import { CountryContext } from '../context/CountryContext';
5 |
6 |
7 | const title = 'WhatsApp without saving number - WhatsApp Anyone app';
8 | const description = 'Use this simple trick to send WhatsApp message without saving contact. This tool opens WhatsApp chat with unsaved number. For quick access, use our "Add To Home Screen" feature';
9 | const websiteUrl = "https://whatsapp-anyone.pgxplorer.dev";
10 |
11 | const App = () => {
12 | const [countryFromIp, setCountryFromIp] = useState();
13 |
14 | useEffect(() => {
15 | const lastSelectedCountryIso2 = localStorage.getItem("lastSelectedCountryIso2");
16 | if (lastSelectedCountryIso2) {
17 | setCountryFromIp(lastSelectedCountryIso2);
18 | } else {
19 | fetch('http://ip-api.com/json/?fields=16387')
20 | .then(res => res.json())
21 | .then((res) => {
22 | if (res?.status === 'success' && res.countryCode) {
23 | setCountryFromIp(res.countryCode.toLowerCase());
24 | } else {
25 | setCountryFromIp('in')
26 | }
27 | })
28 | .catch(err => console.error(err));
29 | }
30 | }, []);
31 | return (
32 | <>
33 |
34 |
35 |
36 | {title}
37 |
38 | {/* Primary Meta Tags */}
39 |
40 |
41 |
42 | {/* Open Graph / Facebook */}
43 |
44 |
45 |
46 |
47 |
48 |
49 | {/* Twitter */}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | >
60 | )
61 | };
62 |
63 | export default App;
--------------------------------------------------------------------------------
/posthog.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/posthog.js
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/mstile-144x144.png
--------------------------------------------------------------------------------
/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/mstile-310x150.png
--------------------------------------------------------------------------------
/public/favicon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/mstile-310x310.png
--------------------------------------------------------------------------------
/public/favicon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/favicon/mstile-70x70.png
--------------------------------------------------------------------------------
/public/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
15 |
34 |
37 |
39 |
41 |
42 |
43 |
45 |
46 |
48 |
49 |
51 |
52 |
53 |
55 |
57 |
59 |
61 |
63 |
65 |
67 |
69 |
71 |
73 |
75 |
77 |
79 |
81 |
83 |
84 |
85 |
87 |
88 |
89 |
91 |
92 |
93 |
95 |
97 |
98 |
99 |
101 |
102 |
104 |
106 |
108 |
110 |
111 |
113 |
115 |
117 |
119 |
121 |
123 |
125 |
128 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Whatsapp Anyone",
3 | "short_name": "Whatsapp Anyone",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#191919",
17 | "background_color": "#191919",
18 | "start_url": "/",
19 | "display": "standalone"
20 | }
21 |
--------------------------------------------------------------------------------
/public/icons/double-tick.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/icons/icon-sq.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/icons/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/icons/moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/icons/moon.png
--------------------------------------------------------------------------------
/public/icons/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/icons/sun.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Whatsapp Anyone",
3 | "short_name": "WA Anyone",
4 | "theme_color": "#2196f3",
5 | "background_color": "#ffffff",
6 | "display": "standalone",
7 | "scope": "/",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "favicon/android-chrome-192x192.png",
12 | "sizes": "192x192",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "favicon/android-chrome-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/public/meta-image-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/meta-image-01.png
--------------------------------------------------------------------------------
/public/meta-image-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/meta-image-02.png
--------------------------------------------------------------------------------
/public/meta-image-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/public/meta-image-03.png
--------------------------------------------------------------------------------
/screenshot-iphonex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/screenshot-iphonex.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTxplorer/whatsapp-anyone/4245b921fe8a9b2f811cbce5ef49f9c2e851f01e/screenshot.png
--------------------------------------------------------------------------------
/service-worker.js:
--------------------------------------------------------------------------------
1 | try{self["workbox:core:5.1.3"]&&_()}catch(e){}const e=(e,...t)=>{let s=e;return t.length>0&&(s+=" :: "+JSON.stringify(t)),s};class t extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:5.1.3"]&&_()}catch(e){}const s=e=>e&&"object"==typeof e?e:{handle:e};class n{constructor(e,t,n="GET"){this.handler=s(t),this.match=e,this.method=n}}class i extends n{constructor(e,t,s){super(({url:t})=>{const s=e.exec(t.href);if(s&&(t.origin===location.origin||0===s.index))return s.slice(1)},t,s)}}const r=e=>new URL(String(e),location.href).href.replace(new RegExp("^"+location.origin),"");class a{constructor(){this.t=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)})}addCacheListener(){self.addEventListener("message",e=>{if(e.data&&"CACHE_URLS"===e.data.type){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(e=>{"string"==typeof e&&(e=[e]);const t=new Request(...e);return this.handleRequest({request:t})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const{params:n,route:i}=this.findMatchingRoute({url:s,request:e,event:t});let r,a=i&&i.handler;if(!a&&this.s&&(a=this.s),a){try{r=a.handle({url:s,request:e,event:t,params:n})}catch(e){r=Promise.reject(e)}return r instanceof Promise&&this.i&&(r=r.catch(n=>this.i.handle({url:s,request:e,event:t}))),r}}findMatchingRoute({url:e,request:t,event:s}){const n=this.t.get(t.method)||[];for(const i of n){let n;const r=i.match({url:e,request:t,event:s});if(r)return n=r,(Array.isArray(r)&&0===r.length||r.constructor===Object&&0===Object.keys(r).length||"boolean"==typeof r)&&(n=void 0),{route:i,params:n}}return{}}setDefaultHandler(e){this.s=s(e)}setCatchHandler(e){this.i=s(e)}registerRoute(e){this.t.has(e.method)||this.t.set(e.method,[]),this.t.get(e.method).push(e)}unregisterRoute(e){if(!this.t.has(e.method))throw new t("unregister-route-but-not-found-with-method",{method:e.method});const s=this.t.get(e.method).indexOf(e);if(!(s>-1))throw new t("unregister-route-route-not-registered");this.t.get(e.method).splice(s,1)}}let c;const o=()=>(c||(c=new a,c.addFetchListener(),c.addCacheListener()),c);const h={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},u=e=>[h.prefix,e,h.suffix].filter(e=>e&&e.length>0).join("-"),l=e=>e||u(h.precache),f=e=>e||u(h.runtime);function w(e){e.then(()=>{})}const d=new Set;class p{constructor(e,t,{onupgradeneeded:s,onversionchange:n}={}){this.o=null,this.h=e,this.u=t,this.l=s,this.p=n||(()=>this.close())}get db(){return this.o}async open(){if(!this.o)return this.o=await new Promise((e,t)=>{let s=!1;setTimeout(()=>{s=!0,t(new Error("The open request was blocked and timed out"))},this.OPEN_TIMEOUT);const n=indexedDB.open(this.h,this.u);n.onerror=()=>t(n.error),n.onupgradeneeded=e=>{s?(n.transaction.abort(),n.result.close()):"function"==typeof this.l&&this.l(e)},n.onsuccess=()=>{const t=n.result;s?t.close():(t.onversionchange=this.p.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,s){return await this.getAllMatching(e,{query:t,count:s})}async getAllKeys(e,t,s){return(await this.getAllMatching(e,{query:t,count:s,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:s=null,direction:n="next",count:i,includeKeys:r=!1}={}){return await this.transaction([e],"readonly",(a,c)=>{const o=a.objectStore(e),h=t?o.index(t):o,u=[],l=h.openCursor(s,n);l.onsuccess=()=>{const e=l.result;e?(u.push(r?e:e.value),i&&u.length>=i?c(u):e.continue()):c(u)}})}async transaction(e,t,s){return await this.open(),await new Promise((n,i)=>{const r=this.o.transaction(e,t);r.onabort=()=>i(r.error),r.oncomplete=()=>n(),s(r,e=>n(e))})}async g(e,t,s,...n){return await this.transaction([t],s,(s,i)=>{const r=s.objectStore(t),a=r[e].apply(r,n);a.onsuccess=()=>i(a.result)})}close(){this.o&&(this.o.close(),this.o=null)}}p.prototype.OPEN_TIMEOUT=2e3;const y={readonly:["get","count","getKey","getAll","getAllKeys"],readwrite:["add","put","clear","delete"]};for(const[e,t]of Object.entries(y))for(const s of t)s in IDBObjectStore.prototype&&(p.prototype[s]=async function(t,...n){return await this.g(s,t,e,...n)});try{self["workbox:expiration:5.1.3"]&&_()}catch(e){}const g=e=>{const t=new URL(e,location.href);return t.hash="",t.href};class m{constructor(e){this.m=e,this.o=new p("workbox-expiration",1,{onupgradeneeded:e=>this.q(e)})}q(e){const t=e.target.result.createObjectStore("cache-entries",{keyPath:"id"});t.createIndex("cacheName","cacheName",{unique:!1}),t.createIndex("timestamp","timestamp",{unique:!1}),(async e=>{await new Promise((t,s)=>{const n=indexedDB.deleteDatabase(e);n.onerror=()=>{s(n.error)},n.onblocked=()=>{s(new Error("Delete blocked"))},n.onsuccess=()=>{t()}})})(this.m)}async setTimestamp(e,t){const s={url:e=g(e),timestamp:t,cacheName:this.m,id:this.v(e)};await this.o.put("cache-entries",s)}async getTimestamp(e){return(await this.o.get("cache-entries",this.v(e))).timestamp}async expireEntries(e,t){const s=await this.o.transaction("cache-entries","readwrite",(s,n)=>{const i=s.objectStore("cache-entries").index("timestamp").openCursor(null,"prev"),r=[];let a=0;i.onsuccess=()=>{const s=i.result;if(s){const n=s.value;n.cacheName===this.m&&(e&&n.timestamp=t?r.push(s.value):a++),s.continue()}else n(r)}}),n=[];for(const e of s)await this.o.delete("cache-entries",e.id),n.push(e.url);return n}v(e){return this.m+"|"+g(e)}}class q{constructor(e,t={}){this.R=!1,this._=!1,this.U=t.maxEntries,this.L=t.maxAgeSeconds,this.m=e,this.N=new m(e)}async expireEntries(){if(this.R)return void(this._=!0);this.R=!0;const e=this.L?Date.now()-1e3*this.L:0,t=await this.N.expireEntries(e,this.U),s=await self.caches.open(this.m);for(const e of t)await s.delete(e);this.R=!1,this._&&(this._=!1,w(this.expireEntries()))}async updateTimestamp(e){await this.N.setTimestamp(e,Date.now())}async isURLExpired(e){if(this.L){return await this.N.getTimestamp(e)e.filter(e=>t in e),R=async({request:e,mode:t,plugins:s=[]})=>{const n=v(s,"cacheKeyWillBeUsed");let i=e;for(const e of n)i=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:i}),"string"==typeof i&&(i=new Request(i));return i},b=async({cacheName:e,request:t,event:s,matchOptions:n,plugins:i=[]})=>{const r=await self.caches.open(e),a=await R({plugins:i,request:t,mode:"read"});let c=await r.match(a,n);for(const t of i)if("cachedResponseWillBeUsed"in t){const i=t.cachedResponseWillBeUsed;c=await i.call(t,{cacheName:e,event:s,matchOptions:n,cachedResponse:c,request:a})}return c},x=async({cacheName:e,request:s,response:n,event:i,plugins:a=[],matchOptions:c})=>{const o=await R({plugins:a,request:s,mode:"write"});if(!n)throw new t("cache-put-with-no-response",{url:r(o.url)});const h=await(async({request:e,response:t,event:s,plugins:n=[]})=>{let i=t,r=!1;for(const t of n)if("cacheWillUpdate"in t){r=!0;const n=t.cacheWillUpdate;if(i=await n.call(t,{request:e,response:i,event:s}),!i)break}return r||(i=i&&200===i.status?i:void 0),i||null})({event:i,plugins:a,response:n,request:o});if(!h)return;const u=await self.caches.open(e),l=v(a,"cacheDidUpdate"),f=l.length>0?await b({cacheName:e,matchOptions:c,request:o}):null;try{await u.put(o,h)}catch(e){throw"QuotaExceededError"===e.name&&await async function(){for(const e of d)await e()}(),e}for(const t of l)await t.cacheDidUpdate.call(t,{cacheName:e,event:i,oldResponse:f,newResponse:h,request:o})},U=b,L=async({request:e,fetchOptions:s,event:n,plugins:i=[]})=>{if("string"==typeof e&&(e=new Request(e)),n instanceof FetchEvent&&n.preloadResponse){const e=await n.preloadResponse;if(e)return e}const r=v(i,"fetchDidFail"),a=r.length>0?e.clone():null;try{for(const t of i)if("requestWillFetch"in t){const s=t.requestWillFetch,i=e.clone();e=await s.call(t,{request:i,event:n})}}catch(e){throw new t("plugin-error-request-will-fetch",{thrownError:e})}const c=e.clone();try{let t;t="navigate"===e.mode?await fetch(e):await fetch(e,s);for(const e of i)"fetchDidSucceed"in e&&(t=await e.fetchDidSucceed.call(e,{event:n,request:c,response:t}));return t}catch(e){for(const t of r)await t.fetchDidFail.call(t,{error:e,event:n,originalRequest:a.clone(),request:c.clone()});throw e}};try{self["workbox:strategies:5.1.3"]&&_()}catch(e){}const N={cacheWillUpdate:async({response:e})=>200===e.status||0===e.status?e:null};let T;async function K(e,t){const s=e.clone(),n={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},i=t?t(n):n,r=function(){if(void 0===T){const e=new Response("");if("body"in e)try{new Response(e.body),T=!0}catch(e){T=!1}T=!1}return T}()?s.body:await s.blob();return new Response(r,i)}try{self["workbox:precaching:5.1.3"]&&_()}catch(e){}function P(e){if(!e)throw new t("add-to-cache-list-unexpected-type",{entry:e});if("string"==typeof e){const t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}const{revision:s,url:n}=e;if(!n)throw new t("add-to-cache-list-unexpected-type",{entry:e});if(!s){const e=new URL(n,location.href);return{cacheKey:e.href,url:e.href}}const i=new URL(n,location.href),r=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",s),{cacheKey:i.href,url:r.href}}class E{constructor(e){this.m=l(e),this.T=new Map,this.K=new Map,this.P=new Map}addToCacheList(e){const s=[];for(const n of e){"string"==typeof n?s.push(n):n&&void 0===n.revision&&s.push(n.url);const{cacheKey:e,url:i}=P(n),r="string"!=typeof n&&n.revision?"reload":"default";if(this.T.has(i)&&this.T.get(i)!==e)throw new t("add-to-cache-list-conflicting-entries",{firstEntry:this.T.get(i),secondEntry:e});if("string"!=typeof n&&n.integrity){if(this.P.has(e)&&this.P.get(e)!==n.integrity)throw new t("add-to-cache-list-conflicting-integrities",{url:i});this.P.set(e,n.integrity)}if(this.T.set(i,e),this.K.set(i,r),s.length>0){const e=`Workbox is precaching URLs without revision info: ${s.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}async install({event:e,plugins:t}={}){const s=[],n=[],i=await self.caches.open(this.m),r=await i.keys(),a=new Set(r.map(e=>e.url));for(const[e,t]of this.T)a.has(t)?n.push(e):s.push({cacheKey:t,url:e});const c=s.map(({cacheKey:s,url:n})=>{const i=this.P.get(s),r=this.K.get(n);return this.O({cacheKey:s,cacheMode:r,event:e,integrity:i,plugins:t,url:n})});return await Promise.all(c),{updatedURLs:s.map(e=>e.url),notUpdatedURLs:n}}async activate(){const e=await self.caches.open(this.m),t=await e.keys(),s=new Set(this.T.values()),n=[];for(const i of t)s.has(i.url)||(await e.delete(i),n.push(i.url));return{deletedURLs:n}}async O({cacheKey:e,url:s,cacheMode:n,event:i,plugins:r,integrity:a}){const c=new Request(s,{integrity:a,cache:n,credentials:"same-origin"});let o,h=await L({event:i,plugins:r,request:c});for(const e of r||[])"cacheWillUpdate"in e&&(o=e);if(!(o?await o.cacheWillUpdate({event:i,request:c,response:h}):h.status<400))throw new t("bad-precaching-response",{url:s,status:h.status});h.redirected&&(h=await K(h)),await x({event:i,plugins:r,response:h,request:e===s?c:new Request(e),cacheName:this.m,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this.T}getCachedURLs(){return[...this.T.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this.T.get(t.href)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s){return(await self.caches.open(this.m)).match(s)}}createHandler(e=!0){return async({request:s})=>{try{const e=await this.matchPrecache(s);if(e)return e;throw new t("missing-precache-entry",{cacheName:this.m,url:s instanceof Request?s.url:s})}catch(t){if(e)return fetch(s);throw t}}}createHandlerBoundToURL(e,s=!0){if(!this.getCacheKeyForURL(e))throw new t("non-precached-url",{url:e});const n=this.createHandler(s),i=new Request(e);return()=>n({request:i})}}let O;const M=()=>(O||(O=new E),O);const k=(e,t)=>{const s=M().getURLsToCacheKeys();for(const n of function*(e,{ignoreURLParametersMatching:t,directoryIndex:s,cleanURLs:n,urlManipulation:i}={}){const r=new URL(e,location.href);r.hash="",yield r.href;const a=function(e,t=[]){for(const s of[...e.searchParams.keys()])t.some(e=>e.test(s))&&e.searchParams.delete(s);return e}(r,t);if(yield a.href,s&&a.pathname.endsWith("/")){const e=new URL(a.href);e.pathname+=s,yield e.href}if(n){const e=new URL(a.href);e.pathname+=".html",yield e.href}if(i){const e=i({url:r});for(const t of e)yield t.href}}(e,t)){const e=s.get(n);if(e)return e}};let S=!1;function j(e){S||((({ignoreURLParametersMatching:e=[/^utm_/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={})=>{const i=l();self.addEventListener("fetch",r=>{const a=k(r.request.url,{cleanURLs:s,directoryIndex:t,ignoreURLParametersMatching:e,urlManipulation:n});if(!a)return;let c=self.caches.open(i).then(e=>e.match(a)).then(e=>e||fetch(a));r.respondWith(c)})})(e),S=!0)}const D=[],C={get:()=>D,add(e){D.push(...e)}},B=e=>{const t=M(),s=C.get();e.waitUntil(t.install({event:e,plugins:s}).catch(e=>{throw e}))},I=e=>{const t=M();e.waitUntil(t.activate())};var A;self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),A={},function(e){M().addToCacheList(e),e.length>0&&(self.addEventListener("install",B),self.addEventListener("activate",I))}([{url:"_next/static/ZT_k64iNSQXyPqwBwO7GK/_buildManifest.js",revision:"fb96ae7926f5104f50f0cf1b3a23a9b5"},{url:"_next/static/ZT_k64iNSQXyPqwBwO7GK/_ssgManifest.js",revision:"abee47769bf307639ace4945f9cfd4ff"},{url:"_next/static/ZT_k64iNSQXyPqwBwO7GK/pages/_app.js",revision:"df2b3595a32e664fc7592ed127f1a1a2"},{url:"_next/static/ZT_k64iNSQXyPqwBwO7GK/pages/_error.js",revision:"28537065789c48a8fdabff7cd54fb2be"},{url:"_next/static/ZT_k64iNSQXyPqwBwO7GK/pages/index.js",revision:"18eb07c19c7cc5f68471baa657290e84"},{url:"_next/static/chunks/commons.3297d2a2d38dac4ad796.js",revision:"fb2a0fddbe705e2d64aaaaf9fcd02a7a"},{url:"_next/static/chunks/framework.e84fa698c7ee940652bd.js",revision:"0b711c3e02b0095b778e8d3a6cd216d2"},{url:"_next/static/runtime/main-14fcabefdfc8d1b2b413.js",revision:"c99b6810b98c8d2a98c0cb31201eb624"},{url:"_next/static/runtime/polyfills-db3eeaf8fbc6db3a50d4.js",revision:"cf3e362240114c40790b57c8ebecab35"},{url:"_next/static/runtime/webpack-c212667a5f965e81e004.js",revision:"f5e6e2fca3144cc944812cfa3547f475"}]),j(A),function(e,s,r){let a;if("string"==typeof e){const t=new URL(e,location.href);a=new n(({url:e})=>e.href===t.href,s,r)}else if(e instanceof RegExp)a=new i(e,s,r);else if("function"==typeof e)a=new n(e,s,r);else{if(!(e instanceof n))throw new t("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=e}o().registerRoute(a)}(/^https?.*/,new class{constructor(e={}){if(this.m=f(e.cacheName),e.plugins){const t=e.plugins.some(e=>!!e.cacheWillUpdate);this.M=t?e.plugins:[N,...e.plugins]}else this.M=[N];this.k=e.networkTimeoutSeconds||0,this.S=e.fetchOptions,this.j=e.matchOptions}async handle({event:e,request:s}){const n=[];"string"==typeof s&&(s=new Request(s));const i=[];let r;if(this.k){const{id:t,promise:a}=this.D({request:s,event:e,logs:n});r=t,i.push(a)}const a=this.C({timeoutId:r,request:s,event:e,logs:n});i.push(a);let c=await Promise.race(i);if(c||(c=await a),!c)throw new t("no-response",{url:s.url});return c}D({request:e,logs:t,event:s}){let n;return{promise:new Promise(t=>{n=setTimeout(async()=>{t(await this.B({request:e,event:s}))},1e3*this.k)}),id:n}}async C({timeoutId:e,request:t,logs:s,event:n}){let i,r;try{r=await L({request:t,event:n,fetchOptions:this.S,plugins:this.M})}catch(e){i=e}if(e&&clearTimeout(e),i||!r)r=await this.B({request:t,event:n});else{const e=r.clone(),s=x({cacheName:this.m,request:t,response:e,event:n,plugins:this.M});if(n)try{n.waitUntil(s)}catch(e){}}return r}B({event:e,request:t}){return U({cacheName:this.m,request:t,event:e,matchOptions:this.j,plugins:this.M})}}({cacheName:"offlineCache",plugins:[new class{constructor(e={}){var t;this.cachedResponseWillBeUsed=async({event:e,request:t,cacheName:s,cachedResponse:n})=>{if(!n)return null;const i=this.I(n),r=this.A(s);w(r.expireEntries());const a=r.updateTimestamp(t.url);if(e)try{e.waitUntil(a)}catch(e){}return i?n:null},this.cacheDidUpdate=async({cacheName:e,request:t})=>{const s=this.A(e);await s.updateTimestamp(t.url),await s.expireEntries()},this.W=e,this.L=e.maxAgeSeconds,this.G=new Map,e.purgeOnQuotaError&&(t=()=>this.deleteCacheAndMetadata(),d.add(t))}A(e){if(e===f())throw new t("expire-custom-caches-only");let s=this.G.get(e);return s||(s=new q(e,this.W),this.G.set(e,s)),s}I(e){if(!this.L)return!0;const t=this.F(e);return null===t||t>=Date.now()-1e3*this.L}F(e){if(!e.headers.has("date"))return null;const t=e.headers.get("date"),s=new Date(t).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[e,t]of this.G)await self.caches.delete(e),await t.delete();this.G=new Map}}({maxEntries:200,purgeOnQuotaError:!0})]}),"GET");
2 |
--------------------------------------------------------------------------------
/styles/global.css:
--------------------------------------------------------------------------------
1 | /* SHAPER */
2 | /* https://hihayk.github.io/shaper/#Manrope,%20Sans Serif/1.32/0.96/1.43/0.71/1.53/0.5/161/100/50/0/0.62/2/false */
3 | html {
4 | --text-xs: 12px;
5 | --text-sm: 16px;
6 | --text-md: 20px;
7 | --text-lg: 26px;
8 | --text-xl: 34px;
9 |
10 | --fontFamily: Manrope, sans-serif;
11 |
12 | --space-sm: 16px;
13 | --space-md: 24px;
14 |
15 | --input-height: 56px;
16 | --input-spacing: 14px;
17 | --input-padding: 12px;
18 |
19 | --c-background: #191919;
20 | --c-color: #ffffff;
21 | --c-colorSecondary: #cccccc;
22 | --c-accent: #00ffaf;
23 | --c-colorInverse: #191919;
24 |
25 | --border-radius: 0.625rem;
26 | --fieldBorderWidth: 2px;
27 |
28 | box-sizing: border-box;
29 |
30 | user-select: none;
31 | -moz-user-select: none;
32 | -webkit-user-select: none;
33 | }
34 |
35 | html[data-theme="light"] {
36 | --c-background: #ffffff;
37 | --c-color: #191919;
38 | --c-colorSecondary: #464646;
39 | --c-accent: #170ac6;
40 | --c-colorInverse: #ffffff;
41 | }
42 |
43 | .moon,
44 | .sun {
45 | border-radius: 50%;
46 | height: 16px;
47 | width: 16px;
48 | cursor: pointer;
49 | }
50 | html[data-theme="dark"] .moon {
51 | display: none;
52 | }
53 | html[data-theme="light"] .sun {
54 | display: none;
55 | }
56 |
57 | body {
58 | background-color: var(--c-background);
59 | color: var(--c-color);
60 | font-family: var(--fontFamily);
61 | margin: 0;
62 | padding: 0;
63 | }
64 |
65 | a,
66 | a:hover,
67 | a:focus,
68 | a:visited,
69 | a:active {
70 | color: var(--c-accent);
71 | }
72 |
73 | .fold {
74 | min-height: min(100vh, 1000px);
75 | width: 100vw;
76 | display: flex;
77 | flex-direction: column;
78 | justify-content: center;
79 | align-items: center;
80 | padding-left: 20px;
81 | padding-right: 20px;
82 | }
83 | .fold2,
84 | .fold3 {
85 | margin: 0 auto;
86 | justify-content: flex-start;
87 | align-items: center;
88 | width: 650px;
89 | max-width: 100%;
90 | }
91 | .fold3 {
92 | text-align: center;
93 | }
94 | .no-minimum {
95 | min-height: unset;
96 | padding-top: 64px;
97 | padding-bottom: 64px;
98 | }
99 |
100 | /* MAIN SECTION */
101 | main {
102 | flex-grow: 1;
103 | display: flex;
104 | flex-direction: column;
105 | justify-content: space-between;
106 | }
107 |
108 | /* TOP SECTION */
109 | header {
110 | display: flex;
111 | flex-direction: column;
112 | align-items: center;
113 | }
114 |
115 | .logo {
116 | height: 100px;
117 | }
118 |
119 | .title {
120 | font-size: var(--text-xl);
121 | text-align: center;
122 | }
123 |
124 | /* FEATURES SECTION */
125 | .features-ul {
126 | font-size: var(--text-sm);
127 | /* padding-left: 20px; */
128 | }
129 | .feature-li {
130 | list-style-image: url(/icons/double-tick.svg);
131 | }
132 |
133 | .feature-li h3 {
134 | margin-top: var(--space-sm);
135 | margin-bottom: 8px;
136 | font-size: var(--text-md);
137 | font-weight: 800;
138 | }
139 |
140 | .feature-li div {
141 | font-weight: var(--text-sm);
142 | opacity: 0.9;
143 | }
144 |
145 | .a2hs {
146 | transition: all 0.5s ease-in;
147 | /* opacity: 0.5; */
148 | }
149 |
150 | .a2hs-loaded {
151 | opacity: 1 !important;
152 | text-decoration: underline;
153 | color: var(--c-accent);
154 | cursor: pointer;
155 | }
156 |
157 | /* INPUT SECTION */
158 | .wa-form .form-inputs {
159 | display: flex;
160 | flex-direction: column;
161 | gap: 12px;
162 | justify-content: center;
163 | align-items: center;
164 | }
165 |
166 | .wa-form .form-inputs > * {
167 | border-radius: var(--border-radius);
168 | outline: none;
169 | flex-grow: 1;
170 | max-width: 350px;
171 | }
172 |
173 |
174 | .wa-form input[type="tel"] {
175 | border-radius: var(--border-radius);
176 | border: var(--fieldBorderWidth) solid var(--c-accent);
177 | outline: none;
178 | font-size: var(--text-sm);
179 | }
180 |
181 | .wa-form button[type="submit"] {
182 | padding: var(--input-padding);
183 | border-radius: var(--border-radius);
184 | border: var(--fieldBorderWidth) solid var(--c-accent);
185 | outline: none;
186 | font-size: var(--text-sm);
187 | }
188 |
189 | /* .phone-input-wrapper {
190 | width: 100%;
191 | user-select: unset;
192 | -moz-user-select: unset;
193 | -webkit-user-select: unset;
194 | } */
195 |
196 | /* .wa-form input[type="tel"] {
197 | width: 100%;
198 | } */
199 |
200 | .country-list-custom {
201 | color: #191919 !important;
202 | font-size: var(--text-sm);
203 | /* -199px is the max-height of dropdown */
204 | /* top: -236px; */
205 | border-radius: var(--border-radius);
206 | }
207 |
208 | .selected-flag-btn-custom .selected-flag:focus {
209 | outline: none;
210 | }
211 | .selected-flag-btn-custom .selected-flag:before {
212 | outline: none;
213 | border-color: transparent !important;
214 | box-shadow: none !important;
215 | }
216 | /* .selected-flag-btn-custom .selected-flag.open:before {
217 | box-shadow: 4px 4px 4px 4px var(--c-accent) !important;
218 | } */
219 |
220 | .wa-form button[type="submit"] {
221 | background-color: var(--c-accent);
222 | color: var(--c-colorInverse);
223 | /* font-size: var(--text-md); */
224 | position: relative;
225 | transition: 0.1s ease-in;
226 | /* margin-left: var(--input-spacing); */
227 | flex-grow: 1;
228 | width: 100%;
229 | max-width: 250px;
230 | cursor: pointer;
231 | }
232 |
233 | .wa-form button[type="submit"]:focus,
234 | .wa-form button[type="submit"]:hover {
235 | top: -1px;
236 | }
237 |
238 | .wa-form button[type="submit"]:active {
239 | top: 2px;
240 | }
241 |
242 | .search-container-custom {
243 | padding: 10px 6px !important;
244 | width: 100% !important;
245 | }
246 | .search-container-custom .search-box {
247 | width: 100% !important;
248 | padding: 8px 4px !important;
249 | margin: 0 !important;
250 | }
251 |
252 | .error {
253 | visibility: hidden;
254 | display: flex;
255 | position: relative;
256 | left: 24px;
257 | }
258 | .error.visible {
259 | visibility: visible;
260 | -webkit-animation: vibrate-2 0.3s linear both;
261 | animation: vibrate-2 0.3s linear both;
262 | border-color: rgb(255, 89, 66) !important;
263 | }
264 | .error-arrow {
265 | transform: rotate(180deg);
266 | }
267 | .error-text {
268 | position: relative;
269 | top: -8px;
270 | left: 4px;
271 | }
272 |
273 |
274 | #feedback-btn {
275 | color: var(--c-accent);
276 | cursor: pointer;
277 | }
278 |
279 | .usecases {
280 | margin: 0 -40px;
281 | max-width: 100vw;
282 | }
283 | .cards-container {
284 | display: flex;
285 | flex-wrap: wrap;
286 | gap: 32px;
287 | }
288 | .card {
289 | display: flex;
290 | flex-direction: column;
291 | flex-basis: calc(50% - 16px);
292 | flex-grow: 1;
293 | }
294 | .card-title,
295 | .card-content {
296 | margin: 0;
297 | text-align: left;
298 | }
299 | .card-content {
300 | color: var(--c-colorSecondary);
301 | }
302 |
303 | .faq-block {
304 | border: 1px solid #aaa;
305 | border-bottom: none;
306 | padding: 1em;
307 | text-align: left;
308 | }
309 | .faq-block:first-of-type {
310 | border-top-left-radius: 8px;
311 | border-top-right-radius: 8px;
312 | }
313 | .faq-block:last-of-type {
314 | border-bottom-left-radius: 8px;
315 | border-bottom-right-radius: 8px;
316 | border: 1px solid #aaa;
317 | }
318 |
319 | .faq-block summary {
320 | font-weight: bold;
321 | cursor: pointer;
322 | margin: -0.5em -0.5em 0;
323 | padding: 0.5em;
324 | }
325 |
326 | .faq-block[open] > summary {
327 | margin-bottom: 0.5em;
328 | }
329 |
330 | footer {
331 | margin: 64px 0 40vh;
332 | font-size: 2em;
333 | text-align: center;
334 | }
335 | footer .mentions {
336 | max-width: 400px;
337 | margin: 0 auto;
338 | padding-top: 24px;
339 | font-size: 14px;
340 | opacity: 0.6;
341 | }
342 |
343 | @media only screen and (min-device-width: 340px) {
344 | .error {
345 | justify-content: center;
346 | }
347 | }
348 |
349 | @media only screen and (max-device-width: 600px) {
350 | footer {
351 | font-size: 1.3em;
352 | }
353 | .usecases {
354 | text-align: left;
355 | }
356 | .cards-container {
357 | flex-direction: column;
358 | }
359 | .card {
360 | flex-basis: 100%;
361 | }
362 | }
363 |
364 | @media only screen and (max-device-height: 600px) {
365 | .logo {
366 | height: 56px;
367 | }
368 |
369 | .title {
370 | font-size: var(--text-lg);
371 | margin-top: 8px;
372 | margin-bottom: 16px;
373 | }
374 | }
375 |
376 | @media only screen and (min-device-height: 800px) and (min-device-width: 320px) {
377 | .feature-li h3 {
378 | margin-top: var(--space-md);
379 | }
380 | }
381 |
382 | /* ----------------------------------------------
383 | * Generated by Animista on 2020-8-4 10:52:1
384 | * Licensed under FreeBSD License.
385 | * See http://animista.net/license for more info.
386 | * w: http://animista.net, t: @cssanimista
387 | * ---------------------------------------------- */
388 |
389 | /**
390 | * ----------------------------------------
391 | * animation vibrate-2
392 | * ----------------------------------------
393 | */
394 | @-webkit-keyframes vibrate-2 {
395 | 0% {
396 | -webkit-transform: translate(0);
397 | transform: translate(0);
398 | }
399 | 20% {
400 | -webkit-transform: translate(2px, -2px);
401 | transform: translate(2px, -2px);
402 | }
403 | 40% {
404 | -webkit-transform: translate(2px, 2px);
405 | transform: translate(2px, 2px);
406 | }
407 | 60% {
408 | -webkit-transform: translate(-2px, 2px);
409 | transform: translate(-2px, 2px);
410 | }
411 | 80% {
412 | -webkit-transform: translate(-2px, -2px);
413 | transform: translate(-2px, -2px);
414 | }
415 | 100% {
416 | -webkit-transform: translate(0);
417 | transform: translate(0);
418 | }
419 | }
420 |
421 | @keyframes vibrate-2 {
422 | 0% {
423 | -webkit-transform: translate(0);
424 | transform: translate(0);
425 | }
426 | 20% {
427 | -webkit-transform: translate(2px, -2px);
428 | transform: translate(2px, -2px);
429 | }
430 | 40% {
431 | -webkit-transform: translate(2px, 2px);
432 | transform: translate(2px, 2px);
433 | }
434 | 60% {
435 | -webkit-transform: translate(-2px, 2px);
436 | transform: translate(-2px, 2px);
437 | }
438 | 80% {
439 | -webkit-transform: translate(-2px, -2px);
440 | transform: translate(-2px, -2px);
441 | }
442 | 100% {
443 | -webkit-transform: translate(0);
444 | transform: translate(0);
445 | }
446 | }
447 |
--------------------------------------------------------------------------------
/utils/graphcms.js:
--------------------------------------------------------------------------------
1 | async function fetchAPI(query, { variables } = {}) {
2 | const res = await fetch(process.env.CMS_LINK, {
3 | method: 'POST',
4 | headers: {
5 | 'Content-Type': 'application/json',
6 | 'Authorization': `Bearer ${process.env.CMS_TOKEN}`
7 | },
8 | body: JSON.stringify({
9 | query,
10 | variables,
11 | }),
12 | });
13 | const json = await res.json();
14 |
15 | if (json.errors) {
16 | console.error(json.errors);
17 | throw new Error('Failed to fetch API');
18 | }
19 |
20 | return json.data;
21 | }
22 |
23 | export async function getAllPosts(projectTag = 'wa_anyone') {
24 | const data = await fetchAPI(`
25 | query AllPosts {
26 | posts(where: {project_contains_some: ${projectTag}}) {
27 | slug
28 | title
29 | excerpt
30 | }
31 | }
32 | `);
33 | return data.posts;
34 | }
35 |
36 | export async function getPostBySlug(slug) {
37 | const data = await fetchAPI(
38 | `
39 | query PostBySlug {
40 | post(where: {slug: "${slug}"}) {
41 | content {
42 | html
43 | }
44 | slug
45 | title
46 | coverImage {
47 | url
48 | altText
49 | }
50 | seoOverride {
51 | title
52 | description
53 | }
54 | }
55 | }
56 | `
57 | );
58 | return data.post;
59 | }
60 |
--------------------------------------------------------------------------------