[web] Make the imported payments code feel at home in the new monorepo

- Use the shared yarn monorepo configuration
- styled-components => emotion (since that's what the rest of the code uses)
- Remove Sentry (since it's gone elsewhere)
This commit is contained in:
Manav Rathi 2024-03-28 11:02:47 +05:30
parent ad3503053d
commit 8f0ef055c5
No known key found for this signature in database
39 changed files with 215 additions and 3907 deletions

1
web/.gitignore vendored
View file

@ -9,6 +9,7 @@ node_modules/
# Local env files
.env
.env.local
.env.*.local
# Next.js

View file

@ -1,13 +0,0 @@
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}

View file

@ -1,3 +0,0 @@
{
"extends": ["next", "next/core-web-vitals"]
}

View file

@ -0,0 +1,8 @@
module.exports = {
extends: ["@/build-config/eslintrc-typescript-react"],
parserOptions: {
tsconfigRootDir: __dirname,
},
// TODO (MR): Figure out a way to not have to ignored the next config .js
ignorePatterns: [".eslintrc.js", "next.config.js"],
};

View file

@ -1,3 +0,0 @@
## Description
## Test Plan

View file

@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View file

@ -1,6 +0,0 @@
{
"tabWidth": 4,
"trailingComma": "es5",
"singleQuote": true,
"jsxBracketSameLine": true
}

View file

@ -1,17 +1,7 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with
[`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
Code that runs on `payments.ente.io`, handling and web facing interaction with
Stripe's API for payments.
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
## Notes
## Development
If you're running this to test out the payment flows end-to-end, please do a
`yarn build`, that will place the output within the `out` folder.
@ -23,12 +13,12 @@ Aside that, these are the necessary configuration changes.
### Local configuration
Update the `.env.local` to point to the local museum instance, and to define the
necessary Stripe keys that can be fetched from [Stripe's developer
Create an `.env` in this directory to point to the local museum instance, and to
define the necessary Stripe keys that can be fetched from [Stripe's developer
dashboard](https://dashboard.stripe.com).
Assuming that your local museum instance is running on `192.168.1.2:8080`, your
`.env.local` should look as follows.
`.env` should look as follows.
```
NEXT_PUBLIC_ENTE_ENDPOINT = http://192.168.1.2:8080
@ -44,15 +34,15 @@ NEXT_PUBLIC_STRIPE_US_PUBLISHABLE_KEY = stripe_publishable_key
3. Update the `whitelisted-redirect-urls` so that it supports redirecting to this locally running project
Assuming that your local payments app is running on `192.168.1.2:3001`, your
Assuming that your local payments app is running on `192.168.1.2:3004`, your
`museum.yaml` should look as follows.
```yaml
stripe:
us:
key: stripe_dev_key
webhook-secret: stripe_dev_webhook_secret
whitelisted-redirect-urls: ["http://192.168.1.2:3001/frameRedirect"]
whitelisted-redirect-urls: ["http://192.168.1.2:3004/frameRedirect"]
path:
success: ?status=success&session_id={CHECKOUT_SESSION_ID}
cancel: ?status=fail&reason=canceled

View file

@ -1,44 +0,0 @@
ente believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us (security@ente.io). We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`E273695C0403F34F74171932DF6DDDE98EBD2394` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of ente. This includes the web app, desktop app,
and mobile apps (iOS and Android). Product downloads are available at https://ente.io. Source
code is available at https://github.com/ente-io.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of ente's issue trackers (https://github.com/ente-io),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Flutter, Next.js etc) which are already reported to the upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under ente's control
- Vulnerabilities in outdated versions of ente
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of ente staff or contractors
- Any physical attempts against ente property or data centers
Thank you for helping keep ente and our users safe!

View file

@ -1,44 +1,18 @@
// This file sets a custom webpack configuration to use your Next.js app
// with Sentry.
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
// @ts-check
const { withSentryConfig } = require('@sentry/nextjs');
const cp = require('child_process');
const gitSha = cp.execSync('git rev-parse --short HEAD', {
cwd: __dirname,
encoding: 'utf8',
});
const moduleExports = {
// Your existing module.exports
output: 'export',
/**
* Configuration for the Next.js build
*
* See also:
* - packages/next/next.config.base.js
* - https://nextjs.org/docs/pages/api-reference/next-config-js
*
* @type {import("next").NextConfig}
*/
const nextConfig = {
/* generate a static export when we run `next build` */
output: "export",
reactStrictMode: true,
env: {
SENTRY_RELEASE: gitSha,
},
sentry: {
hideSourceMaps: false,
},
};
const SentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that
// the following options are set automatically, and overriding them is not
// recommended:
// release, url, org, project, authToken, configFile, stripPrefix,
// urlPrefix, include, ignore
release: gitSha,
silent: true, // Suppresses all logs
// Ignore sentry webpack errors
errorHandler: (err, invokeErr, compilation) => {
compilation.warnings.push('Sentry CLI Plugin: ' + err.message);
},
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options.
};
// Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins
module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions);
module.exports = nextConfig;

View file

@ -1,31 +1,11 @@
{
"name": "payments",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@sentry/nextjs": "^7.54.0",
"@stripe/stripe-js": "^1.17.0",
"axios": "^0.21.1",
"bootstrap": "4.6.0",
"next": "^14.1.4",
"react": "^18.2.0",
"react-bootstrap": "^1.6.1",
"react-dom": "^18.2.0",
"styled-components": "^5.3.0"
},
"devDependencies": {
"@types/node": "20.11.30",
"@types/react": "17.0.15",
"@types/styled-components": "^5.1.12",
"babel-plugin-styled-components": "^1.13.2",
"eslint": "^8.57.0",
"eslint-config-next": "^14.1.4",
"typescript": "^5.4.2"
}
"name": "payments",
"version": "0.0.0",
"private": true,
"dependencies": {
"@/next": "*",
"@stripe/stripe-js": "^1.17.0",
"axios": "^0.21.1",
"bootstrap": "4.6.0"
}
}

View file

@ -1,27 +0,0 @@
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN =
(process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN) ??
'https://67447bc36684b1f7a18d79683b788f25@sentry.ente.io/6';
const TUNNEL_URL = 'https://sentry-reporter.ente.io';
const SENTRY_ENV = process.env.NEXT_PUBLIC_SENTRY_ENV ?? 'development';
Sentry.init({
dsn: SENTRY_DSN,
enabled: false,
environment: SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
attachStacktrace: true,
autoSessionTracking: false,
tunnel: TUNNEL_URL,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});

View file

@ -1,4 +0,0 @@
defaults.url=https://sentry.ente.io/
defaults.org=ente
defaults.project=web-payments

View file

@ -1,26 +0,0 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN =
(process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN) ??
'https://208e398c28cd4c069c83d7c6e63adef6@sentry.ente.io/6';
const SENTRY_ENV = process.env.NEXT_PUBLIC_SENTRY_ENV ?? 'development';
Sentry.init({
dsn: SENTRY_DSN,
enabled: SENTRY_ENV !== 'development',
environment: SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
release: process.env.SENTRY_RELEASE,
autoSessionTracking: false,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});

View file

@ -1,4 +1,4 @@
import styled from 'styled-components';
import styled from "@mui/styled-engine";
export const Container = styled.div`
display: flex;

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Spinner } from 'react-bootstrap';
import { Spinner } from "react-bootstrap";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function EnteSpinner(props: any) {
return (
<Spinner {...props} animation="border" variant="success" role="status">

View file

@ -1,2 +1,2 @@
export const DESKTOP_REDIRECT_URL = 'ente://app/gallery';
export const ENTE_WEBSITE_URL = 'https://ente.io';
export const DESKTOP_REDIRECT_URL = "ente://app/gallery";
export const ENTE_WEBSITE_URL = "https://ente.io";

View file

@ -1,6 +1,5 @@
import { Container } from 'components/Container';
import React from 'react';
import constants from 'utils/strings/constants';
import { Container } from "components/Container";
import constants from "utils/strings/constants";
export default function Home() {
return <Container>{constants.NOT_FOUND}</Container>;

View file

@ -1,9 +1,8 @@
import '../styles/globals.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import type { AppProps } from 'next/app';
import React from 'react';
import constants from 'utils/strings/constants';
import Head from 'next/head';
import "bootstrap/dist/css/bootstrap.min.css";
import type { AppProps } from "next/app";
import Head from "next/head";
import constants from "utils/strings/constants";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return (

View file

@ -1,11 +1,10 @@
import { Container } from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
import { DESKTOP_REDIRECT_URL } from 'constants/common';
import { useRouter } from 'next/dist/client/router';
import React, { useEffect, useState } from 'react';
import { Container } from "components/Container";
import EnteSpinner from "components/EnteSpinner";
import { DESKTOP_REDIRECT_URL } from "constants/common";
import * as React from "react";
export default function DesktopRedirect() {
useEffect(() => {
React.useEffect(() => {
const currentURL = new URL(window.location.href);
const desktopRedirectURL = new URL(DESKTOP_REDIRECT_URL);
desktopRedirectURL.search = currentURL.search;

View file

@ -1,21 +1,22 @@
import { Container } from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
import { ENTE_WEBSITE_URL } from 'constants/common';
import React, { useEffect, useState } from 'react';
import { parseAndHandleRequest } from 'services/billingService';
import { CUSTOM_ERROR } from 'utils/error';
import constants from 'utils/strings/constants';
import { Container } from "components/Container";
import EnteSpinner from "components/EnteSpinner";
import { ENTE_WEBSITE_URL } from "constants/common";
import * as React from "react";
import { parseAndHandleRequest } from "services/billingService";
import { CUSTOM_ERROR } from "utils/error";
import constants from "utils/strings/constants";
export default function Home() {
const [errorMessageView, setErrorMessageView] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
const [errorMessageView, setErrorMessageView] = React.useState(false);
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
async function main() {
try {
setLoading(true);
await parseAndHandleRequest();
} catch (e: any) {
} catch (e: unknown) {
if (
e instanceof Error &&
e.message === CUSTOM_ERROR.DIRECT_OPEN_WITH_NO_QUERY_PARAMS
) {
window.location.href = ENTE_WEBSITE_URL;
@ -24,6 +25,8 @@ export default function Home() {
}
}
}
// TODO: audit
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main();
}, []);

View file

@ -1,4 +1,12 @@
import axios, { AxiosRequestConfig } from 'axios';
// TODO: Audit
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/consistent-indexed-object-style */
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, { AxiosRequestConfig } from "axios";
interface IHTTPHeaders {
[headerKey: string]: any;
@ -29,7 +37,7 @@ class HTTPService {
* header object to be append to all api calls.
*/
private headers: IHTTPHeaders = {
'content-type': 'application/json',
"content-type": "application/json",
};
/**
@ -77,7 +85,9 @@ class HTTPService {
...config.headers,
};
if (customConfig?.cancel) {
config.cancelToken=new axios.CancelToken((c)=> (customConfig.cancel.exec=c));
config.cancelToken = new axios.CancelToken(
(c) => (customConfig.cancel.exec = c),
);
}
return await axios({ ...config, ...customConfig });
}
@ -94,7 +104,7 @@ class HTTPService {
return this.request(
{
headers,
method: 'GET',
method: "GET",
params,
url,
},
@ -116,7 +126,7 @@ class HTTPService {
{
data,
headers,
method: 'POST',
method: "POST",
params,
url,
},
@ -138,7 +148,7 @@ class HTTPService {
{
data,
headers,
method: 'PUT',
method: "PUT",
params,
url,
},
@ -160,7 +170,7 @@ class HTTPService {
{
data,
headers,
method: 'DELETE',
method: "DELETE",
params,
url,
},

View file

@ -1,68 +1,76 @@
import { loadStripe, Stripe } from '@stripe/stripe-js';
import { CUSTOM_ERROR } from 'utils/error';
import { logError } from 'utils/sentry';
import HTTPService from './HTTPService';
// TODO: Audit this and other eslints
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import { loadStripe } from "@stripe/stripe-js";
import { CUSTOM_ERROR } from "utils/error";
import { logError } from "utils/sentry";
import HTTPService from "./HTTPService";
const getStripePublishableKey = (stripeAccount: StripeAccountCountry) => {
if (stripeAccount === StripeAccountCountry.STRIPE_IN) {
return (
process.env.NEXT_PUBLIC_STRIPE_IN_PUBLISHABLE_KEY ??
'pk_live_51HAhqDK59oeucIMOiTI6MDDM2UWUbCAJXJCGsvjJhiO8nYJz38rQq5T4iyQLDMKxqEDUfU5Hopuj4U5U4dff23oT00fHvZeodC'
"pk_live_51HAhqDK59oeucIMOiTI6MDDM2UWUbCAJXJCGsvjJhiO8nYJz38rQq5T4iyQLDMKxqEDUfU5Hopuj4U5U4dff23oT00fHvZeodC"
);
} else if (stripeAccount === StripeAccountCountry.STRIPE_US) {
return (
process.env.NEXT_PUBLIC_STRIPE_US_PUBLISHABLE_KEY ??
'pk_live_51LZ9P4G1ITnQlpAnrP6pcS7NiuJo3SnJ7gibjJlMRatkrd2EY1zlMVTVQG5RkSpLPbsHQzFfnEtgHnk1PiylIFkk00tC0LWXwi'
"pk_live_51LZ9P4G1ITnQlpAnrP6pcS7NiuJo3SnJ7gibjJlMRatkrd2EY1zlMVTVQG5RkSpLPbsHQzFfnEtgHnk1PiylIFkk00tC0LWXwi"
);
} else {
throw Error('stripe account not found');
throw Error("stripe account not found");
}
};
const getEndpoint = () => {
const endPoint =
process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? 'https://api.ente.io';
process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? "https://api.ente.io";
return endPoint;
};
enum PAYMENT_INTENT_STATUS {
SUCCESS = 'success',
REQUIRE_ACTION = 'requires_action',
REQUIRE_PAYMENT_METHOD = 'requires_payment_method',
SUCCESS = "success",
REQUIRE_ACTION = "requires_action",
REQUIRE_PAYMENT_METHOD = "requires_payment_method",
}
enum FAILURE_REASON {
// Unable to authenticate card or 3DS
// User should be showing button for fixing card via customer portal
AUTHENTICATION_FAILED = 'authentication_failed',
AUTHENTICATION_FAILED = "authentication_failed",
// Card declined result in this error. Show button to the customer portal.
REQUIRE_PAYMENT_METHOD = 'requires_payment_method',
STRIPE_ERROR = 'stripe_error',
CANCELED = 'canceled',
SERVER_ERROR = 'server_error',
REQUIRE_PAYMENT_METHOD = "requires_payment_method",
STRIPE_ERROR = "stripe_error",
CANCELED = "canceled",
SERVER_ERROR = "server_error",
}
enum STRIPE_ERROR_TYPE {
CARD_ERROR = 'card_error',
AUTHENTICATION_ERROR = 'authentication_error',
CARD_ERROR = "card_error",
AUTHENTICATION_ERROR = "authentication_error",
}
enum STRIPE_ERROR_CODE {
AUTHENTICATION_ERROR = 'payment_intent_authentication_failure',
AUTHENTICATION_ERROR = "payment_intent_authentication_failure",
}
enum RESPONSE_STATUS {
success = 'success',
fail = 'fail',
success = "success",
fail = "fail",
}
enum PaymentActionType {
Buy = 'buy',
Update = 'update',
Buy = "buy",
Update = "update",
}
enum StripeAccountCountry {
STRIPE_IN = 'IN',
STRIPE_US = 'US',
STRIPE_IN = "IN",
STRIPE_US = "US",
}
interface SubscriptionUpdateResponse {
@ -75,10 +83,10 @@ interface SubscriptionUpdateResponse {
export async function parseAndHandleRequest() {
try {
const urlParams = new URLSearchParams(window.location.search);
const productID = urlParams.get('productID');
const paymentToken = urlParams.get('paymentToken');
const action = urlParams.get('action');
const redirectURL = urlParams.get('redirectURL');
const productID = urlParams.get("productID");
const paymentToken = urlParams.get("paymentToken");
const action = urlParams.get("action");
const redirectURL = urlParams.get("redirectURL");
if (!action && !paymentToken && !productID && !redirectURL) {
throw Error(CUSTOM_ERROR.DIRECT_OPEN_WITH_NO_QUERY_PARAMS);
} else if (!action || !paymentToken || !productID || !redirectURL) {
@ -95,7 +103,7 @@ export async function parseAndHandleRequest() {
throw Error(CUSTOM_ERROR.INVALID_ACTION);
}
} catch (e: any) {
console.error("Error: ", JSON.stringify(e)) ;
console.error("Error: ", JSON.stringify(e));
if (e.message !== CUSTOM_ERROR.DIRECT_OPEN_WITH_NO_QUERY_PARAMS) {
logError(e);
}
@ -110,7 +118,7 @@ async function getUserStripeAccountCountry(
`${getEndpoint()}/billing/stripe-account-country`,
undefined,
{
'X-Auth-Token': paymentToken,
"X-Auth-Token": paymentToken,
},
);
return response.data;
@ -125,11 +133,11 @@ async function getStripe(
const stripe = await loadStripe(publishableKey);
if (!stripe) {
throw Error('stripe load failed');
throw Error("stripe load failed");
}
return stripe;
} catch (e) {
logError(e, 'stripe load failed');
logError(e, "stripe load failed");
redirectToApp(
redirectURL,
RESPONSE_STATUS.fail,
@ -145,9 +153,8 @@ export async function buyPaidSubscription(
redirectURL: string,
) {
try {
const { stripeAccountCountry } = await getUserStripeAccountCountry(
paymentToken,
);
const { stripeAccountCountry } =
await getUserStripeAccountCountry(paymentToken);
const stripe = await getStripe(redirectURL, stripeAccountCountry);
const { sessionID } = await createCheckoutSession(
productID,
@ -158,7 +165,7 @@ export async function buyPaidSubscription(
sessionId: sessionID,
});
} catch (e) {
logError(e, 'subscription purchase failed');
logError(e, "subscription purchase failed");
redirectToApp(
redirectURL,
RESPONSE_STATUS.fail,
@ -180,7 +187,7 @@ async function createCheckoutSession(
redirectURL,
},
{
'X-Auth-Token': paymentToken,
"X-Auth-Token": paymentToken,
},
);
return response.data;
@ -192,9 +199,8 @@ export async function updateSubscription(
redirectURL: string,
) {
try {
const { stripeAccountCountry } = await getUserStripeAccountCountry(
paymentToken,
);
const { stripeAccountCountry } =
await getUserStripeAccountCountry(paymentToken);
const stripe = await getStripe(redirectURL, stripeAccountCountry);
const { result } = await subscriptionUpdateRequest(
paymentToken,
@ -245,7 +251,7 @@ export async function updateSubscription(
}
}
} catch (e) {
logError(e, 'subscription update failed');
logError(e, "subscription update failed");
redirectToApp(
redirectURL,
RESPONSE_STATUS.fail,
@ -266,7 +272,7 @@ async function subscriptionUpdateRequest(
},
undefined,
{
'X-Auth-Token': paymentToken,
"X-Auth-Token": paymentToken,
},
);
return response.data;

View file

@ -1,21 +1,25 @@
/* ubuntu-regular - latin */
@font-face {
font-family: 'Ubuntu';
font-family: "Ubuntu";
font-style: normal;
font-weight: 400;
src: local(''), url('/fonts/ubuntu-v15-latin-regular.woff2') format('woff2'),
src:
local(""),
url("/fonts/ubuntu-v15-latin-regular.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/fonts/ubuntu-v15-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url("/fonts/ubuntu-v15-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* ubuntu-700 - latin */
@font-face {
font-family: 'Ubuntu';
font-family: "Ubuntu";
font-style: normal;
font-weight: 700;
src: local(''), url('/fonts/ubuntu-v15-latin-700.woff2') format('woff2'),
src:
local(""),
url("/fonts/ubuntu-v15-latin-700.woff2") format("woff2"),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/fonts/ubuntu-v15-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url("/fonts/ubuntu-v15-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
html,
body {

View file

@ -1,3 +1,3 @@
export function runningInBrowser() {
return typeof window !== 'undefined';
return typeof window !== "undefined";
}

View file

@ -1,5 +1,5 @@
export const CUSTOM_ERROR = {
DIRECT_OPEN_WITH_NO_QUERY_PARAMS: 'direct open with no query params',
MISSING_REQUIRED_QUERY_PARAM: 'missing required query param',
INVALID_ACTION: 'invalid action',
DIRECT_OPEN_WITH_NO_QUERY_PARAMS: "direct open with no query params",
MISSING_REQUIRED_QUERY_PARAM: "missing required query param",
INVALID_ACTION: "invalid action",
};

View file

@ -1,28 +0,0 @@
import { logError } from 'utils/sentry';
export enum LS_KEYS {
AnonymizeUserID = 'anonymizedUserID',
}
export const getData = (key: LS_KEYS) => {
try {
if (
typeof localStorage === 'undefined' ||
typeof key === 'undefined' ||
typeof localStorage.getItem(key) === 'undefined'
) {
return null;
}
const data = localStorage.getItem(key);
return data && JSON.parse(data);
} catch (e) {
logError(e, 'Failed to Parse JSON');
}
};
export const setData = (key: LS_KEYS, value: object) => {
if (typeof localStorage === 'undefined') {
return null;
}
localStorage.setItem(key, JSON.stringify(value));
};

View file

@ -1,14 +1,3 @@
import * as Sentry from '@sentry/nextjs';
import { getUserAnonymizedID } from 'utils/user';
export const logError = (e: any, msg?: string) => {
Sentry.captureException(e, {
level: "info",
user: { id: getUserAnonymizedID() },
contexts: {
context: {
message: msg,
},
},
});
export const logError = (e: unknown, msg?: string) => {
console.error(msg, e);
};

View file

@ -1,4 +0,0 @@
import { getConstantValue } from './vernacularStrings';
const constants = getConstantValue();
export default constants;

View file

@ -0,0 +1,6 @@
const englishConstants = {
TITLE: "Payments | ente.io",
SOMETHING_WENT_WRONG: "Oops, something went wrong.",
NOT_FOUND: "404 | This page could not be found.",
};
export default englishConstants;

View file

@ -1,6 +0,0 @@
const englishConstants = {
TITLE: 'Payments | ente.io',
SOMETHING_WENT_WRONG: 'Oops, something went wrong.',
NOT_FOUND: '404 | This page could not be found.',
};
export default englishConstants;

View file

@ -1,87 +0,0 @@
import { runningInBrowser } from 'utils/common';
import englishConstants from './englishConstants';
/** Enums of supported locale */
export enum locale {
en = 'en',
hi = 'hi',
}
/**
* Defines a template with placeholders which can then be
* substituted at run time. Enabling the developer to create
* different template for different locale and populate them
* at run time.
*
* @param strings
* @param keys
* @returns string
*/
export function template(
strings: TemplateStringsArray,
...keys: string[] | number[]
) {
return (...values: any[]) => {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach((key, i) => {
const value = typeof key === 'number' ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
/** Type for vernacular string constants */
export type VernacularConstants<T> = {
[locale.en]: T;
[locale.hi]?: {
[x in keyof T]?: string;
};
};
/**
* Returns a valid locale from string and defaults
* to English.
*
* @param lang
*/
export const getLocale = (lang: string) => {
switch (lang) {
case locale.hi:
return locale.hi;
default:
return locale.en;
}
};
/**
* Global constants
*/
const globalConstants: VernacularConstants<typeof englishConstants> = {
en: englishConstants,
};
/**
* Function to extend global constants with local constants
* @param localConstants
*/
export function getConstantValue<T>(localConstants?: VernacularConstants<T>) {
const searchParam = runningInBrowser() ? window.location.search : '';
const query = new URLSearchParams(searchParam);
const currLocale = getLocale(query.get('lang') ?? 'en');
if (currLocale !== 'en') {
return {
...globalConstants.en,
...localConstants?.en,
...globalConstants[currLocale],
...localConstants?.[currLocale],
};
}
return {
...globalConstants[currLocale],
...localConstants?.[currLocale],
};
}

View file

@ -1,23 +0,0 @@
import { getData, LS_KEYS, setData } from 'utils/localStorage';
export function makeID(length: number) {
let result = '';
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength),
);
}
return result;
}
export function getUserAnonymizedID() {
let anonymizeUserID = getData(LS_KEYS.AnonymizeUserID)?.id;
if (!anonymizeUserID) {
anonymizeUserID = makeID(6);
setData(LS_KEYS.AnonymizeUserID, { id: anonymizeUserID });
}
return anonymizeUserID;
}

View file

@ -1,31 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "./src",
"incremental": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "./src",
"incremental": true
},
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "next.config.js"]
}

File diff suppressed because it is too large Load diff

View file

@ -37,10 +37,6 @@
#
# NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3001
# The Ente API endpoint for payments related functionality
#
# NEXT_PUBLIC_ENTE_PAYMENT_ENDPOINT = http://localhost:3001
# The URL for the shared albums deployment
#
# The shared albums are served from the photos app code, and "albums.ente.io" is
@ -68,6 +64,10 @@
#
# NEXT_PUBLIC_ENTE_FAMILY_PORTAL_ENDPOINT = http://localhost:3003
# The Ente API endpoint for payments related functionality
#
# NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3004
# The JSON which describes the expected results of our integration tests. See
# `upload.test.ts` for more details of the expected format.
#

View file

@ -11,16 +11,19 @@
"build:accounts": "yarn workspace accounts next build",
"build:auth": "yarn workspace auth next build",
"build:cast": "yarn workspace cast next build",
"build:payments": "yarn workspace payments next build",
"build:photos": "yarn workspace photos next build",
"deploy:accounts": "open 'https://github.com/ente-io/ente/compare/deploy/accounts...main?quick_pull=1&title=[web]+Deploy+accounts&body=Deploy+accounts.ente.io'",
"deploy:auth": "open 'https://github.com/ente-io/ente/compare/deploy/auth...main?quick_pull=1&title=[web]+Deploy+auth&body=Deploy+auth.ente.io'",
"deploy:cast": "open 'https://github.com/ente-io/ente/compare/deploy/cast...main?quick_pull=1&title=[web]+Deploy+cast&body=Deploy+cast.ente.io'",
"deploy:payments": "open 'https://github.com/ente-io/ente/compare/deploy/payments...main?quick_pull=1&title=[web]+Deploy+payments&body=Deploy+payments.ente.io'",
"deploy:photos": "open 'https://github.com/ente-io/ente/compare/deploy/photos...main?quick_pull=1&title=[web]+Deploy+photos&body=Deploy+web.ente.io'",
"dev": "yarn dev:photos",
"dev:accounts": "yarn workspace accounts next dev",
"dev:accounts": "yarn workspace accounts next dev -p 3001",
"dev:albums": "yarn workspace photos next dev -p 3002",
"dev:auth": "yarn workspace auth next dev",
"dev:cast": "yarn workspace cast next dev",
"dev:payments": "yarn workspace photos next dev -p 3004",
"dev:photos": "yarn workspace photos next dev",
"lint": "yarn prettier --check . && yarn workspaces run eslint .",
"lint-fix": "yarn prettier --write . && yarn workspaces run eslint --fix ."

View file

@ -70,14 +70,6 @@ export const getAccountsURL = () => {
return `https://accounts.ente.io`;
};
export const getPaymentsURL = () => {
const paymentsURL = process.env.NEXT_PUBLIC_ENTE_PAYMENT_ENDPOINT;
if (paymentsURL) {
return paymentsURL;
}
return `https://payments.ente.io`;
};
export const getAlbumsURL = () => {
const albumsURL = process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT;
if (albumsURL) {
@ -97,3 +89,14 @@ export const getFamilyPortalURL = () => {
}
return `https://family.ente.io`;
};
/**
* Return the URL for the host that handles payment related functionality.
*/
export const getPaymentsURL = () => {
const paymentsURL = process.env.NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT;
if (paymentsURL) {
return paymentsURL;
}
return `https://payments.ente.io`;
};

View file

@ -538,7 +538,7 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz#2d4260033e199b3032a08b41348ac10de21c47e9"
integrity sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==
"@stripe/stripe-js@^1.13.2":
"@stripe/stripe-js@^1.13.2", "@stripe/stripe-js@^1.17.0":
version "1.54.2"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.54.2.tgz#0665848e22cbda936cfd05256facdfbba121438d"
integrity sha512-R1PwtDvUfs99cAjfuQ/WpwJ3c92+DAMy9xGApjqlWQMj0FKQabUAys2swfTRNzuYAYJh7NqK2dzcYVNkKLEKUg==
@ -1151,6 +1151,13 @@ axe-core@=4.7.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
axios@^0.21.1:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
axios@^1.6.7:
version "1.6.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
@ -1210,6 +1217,11 @@ blazeface-back@^0.0.9:
resolved "https://registry.yarnpkg.com/blazeface-back/-/blazeface-back-0.0.9.tgz#a8a26a0022950eb21136693f2fca3c52315ad2a4"
integrity sha512-t0i5V117j074d7d7mlLaRq9n/bYchXcSEgpWVbGGloV68A6Jn22t4SNoEC3t+MOsU8H+eXoDv2/6+JsqActM1g==
bootstrap@4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7"
integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==
bootstrap@^4.5.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.2.tgz#8e0cd61611728a5bf65a3a2b8d6ff6c77d5d7479"
@ -2042,6 +2054,11 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
follow-redirects@^1.14.0:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
follow-redirects@^1.15.4:
version "1.15.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"