feat: add success and fail payment pages, fix payment system
This commit is contained in:
parent
237dec5681
commit
1fc35e973d
BIN
public/ExclamationIcon.png
Normal file
BIN
public/ExclamationIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/SuccessIcon.png
Normal file
BIN
public/SuccessIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@ -1,85 +1,121 @@
|
|||||||
import routes from "@/routes"
|
import routes from "@/routes";
|
||||||
import { AuthPayload } from "../types"
|
import { AuthPayload } from "../types";
|
||||||
import { getAuthHeaders } from "../utils"
|
import { getAuthHeaders } from "../utils";
|
||||||
|
|
||||||
export interface GetPayload extends AuthPayload {
|
export interface GetPayload extends AuthPayload {
|
||||||
id: string
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChargebeeReceiptPayload extends AuthPayload {
|
export interface ChargebeeReceiptPayload extends AuthPayload {
|
||||||
itemPriceId: string
|
itemPriceId: string;
|
||||||
gwToken: string
|
gwToken: string;
|
||||||
referenceId?: string
|
referenceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppleReceiptPayload extends AuthPayload {
|
export interface AppleReceiptPayload extends AuthPayload {
|
||||||
receiptData: string
|
receiptData: string;
|
||||||
autorenewable?: boolean
|
autorenewable?: boolean;
|
||||||
sandbox?: boolean
|
sandbox?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Payload = ChargebeeReceiptPayload | AppleReceiptPayload
|
export interface StripeReceiptPayload extends AuthPayload {
|
||||||
|
itemInterval: "week" | "month" | "year";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Payload =
|
||||||
|
| ChargebeeReceiptPayload
|
||||||
|
| AppleReceiptPayload
|
||||||
|
| StripeReceiptPayload;
|
||||||
|
|
||||||
export interface Response {
|
export interface Response {
|
||||||
subscription_receipt: SubscriptionReceipt
|
subscription_receipt: SubscriptionReceipt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubscriptionReceipt {
|
export interface SubscriptionReceipt {
|
||||||
id: string
|
id: string;
|
||||||
user_id: number
|
user_id: number;
|
||||||
status: number
|
status: number;
|
||||||
expires_at: null | string
|
expires_at: null | string;
|
||||||
requested_at: string
|
requested_at: string;
|
||||||
created_at: string
|
created_at: string;
|
||||||
data: {
|
data: {
|
||||||
input: {
|
input: {
|
||||||
subscription_items: [
|
subscription_items: [
|
||||||
{
|
{
|
||||||
item_price_id: string
|
item_price_id: string;
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
payment_intent: {
|
payment_intent: {
|
||||||
gw_token: string
|
gw_token: string;
|
||||||
gateway_account_id: string
|
gateway_account_id: string;
|
||||||
}
|
};
|
||||||
},
|
};
|
||||||
app_bundle_id: string
|
client_secret: string;
|
||||||
autorenewable: boolean
|
app_bundle_id: string;
|
||||||
error: string
|
autorenewable: boolean;
|
||||||
}
|
error: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRequest({ token, itemPriceId, gwToken, referenceId }: ChargebeeReceiptPayload): Request
|
function createRequest({
|
||||||
function createRequest({ token, receiptData, autorenewable = true, sandbox = true }: AppleReceiptPayload): Request
|
token,
|
||||||
function createRequest(payload: Payload): Request
|
itemPriceId,
|
||||||
|
gwToken,
|
||||||
|
referenceId,
|
||||||
|
}: ChargebeeReceiptPayload): Request;
|
||||||
|
function createRequest({
|
||||||
|
token,
|
||||||
|
receiptData,
|
||||||
|
autorenewable = true,
|
||||||
|
sandbox = true,
|
||||||
|
}: AppleReceiptPayload): Request;
|
||||||
|
function createRequest({ token, itemInterval }: StripeReceiptPayload): Request;
|
||||||
|
function createRequest(payload: Payload): Request;
|
||||||
function createRequest(payload: Payload): Request {
|
function createRequest(payload: Payload): Request {
|
||||||
const url = new URL(routes.server.subscriptionReceipts())
|
const url = new URL(routes.server.subscriptionReceipts());
|
||||||
const data = isChargebeeReceipt(payload) ? {
|
const data = getDataPayload(payload);
|
||||||
way: 'chargebee',
|
const body = JSON.stringify(data);
|
||||||
subscription_receipt: {
|
return new Request(url, {
|
||||||
item_price_id: payload.itemPriceId,
|
method: "POST",
|
||||||
gw_token: payload.gwToken,
|
headers: getAuthHeaders(payload.token),
|
||||||
reference_id: payload.referenceId,
|
body,
|
||||||
}
|
});
|
||||||
} : {
|
|
||||||
way: 'apple',
|
|
||||||
subscription_receipt: {
|
|
||||||
receipt_data: payload.receiptData,
|
|
||||||
autorenewable: payload.autorenewable,
|
|
||||||
sandbox: payload.sandbox,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const body = JSON.stringify(data)
|
|
||||||
return new Request(url, { method: 'POST', headers: getAuthHeaders(payload.token), body })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChargebeeReceipt(payload: Payload ): payload is ChargebeeReceiptPayload {
|
function getDataPayload(payload: Payload) {
|
||||||
return 'itemPriceId' in payload && 'gwToken' in payload
|
if ("itemPriceId" in payload && "gwToken" in payload) {
|
||||||
|
return {
|
||||||
|
way: "chargebee",
|
||||||
|
subscription_receipt: {
|
||||||
|
item_price_id: payload.itemPriceId,
|
||||||
|
gw_token: payload.gwToken,
|
||||||
|
reference_id: payload.referenceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("receiptData" in payload) {
|
||||||
|
return {
|
||||||
|
way: "apple",
|
||||||
|
subscription_receipt: {
|
||||||
|
receipt_data: payload.receiptData,
|
||||||
|
autorenewable: payload.autorenewable,
|
||||||
|
sandbox: payload.sandbox,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("itemInterval" in payload) {
|
||||||
|
return {
|
||||||
|
way: "stripe",
|
||||||
|
subscription_receipt: {
|
||||||
|
item_interval: payload.itemInterval,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGetRequest({ id, token }: GetPayload): Request {
|
function createGetRequest({ id, token }: GetPayload): Request {
|
||||||
const url = new URL(routes.server.subscriptionReceipt(id))
|
const url = new URL(routes.server.subscriptionReceipt(id));
|
||||||
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createRequest, createGetRequest }
|
export { createRequest, createGetRequest };
|
||||||
|
|||||||
@ -45,12 +45,16 @@ import { EPathsFromHome } from "@/store/siteConfig";
|
|||||||
import parseAPNG, { APNG } from "apng-js";
|
import parseAPNG, { APNG } from "apng-js";
|
||||||
import { useApi, useApiCall } from "@/api";
|
import { useApi, useApiCall } from "@/api";
|
||||||
import { Asset } from "@/api/resources/Assets";
|
import { Asset } from "@/api/resources/Assets";
|
||||||
|
import PaymentResultPage from "../PaymentPage/results";
|
||||||
|
import PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
|
||||||
|
import PaymentFailPage from "../PaymentPage/results/ErrorPage";
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
||||||
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const closeSpecialOfferAttention = () => {
|
const closeSpecialOfferAttention = () => {
|
||||||
setIsSpecialOfferOpen(false);
|
setIsSpecialOfferOpen(false);
|
||||||
@ -66,6 +70,15 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
const { data } = useApiCall<Asset[]>(assetsData);
|
const { data } = useApiCall<Asset[]>(assetsData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// TODO: remove later
|
||||||
|
dispatch(
|
||||||
|
actions.token.update(
|
||||||
|
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjQwNjEyLCJpYXQiOjE2OTc5MjY0MTksImV4cCI6MTcwNjU2NjQxOSwianRpIjoiZTg0NWE0ZmUtYmVmNy00ODNmLWIwMzgtYjlkYzBlZjk1MjNmIiwiZW1haWwiOiJvdGhlcjJAZXhhbXBsZS5jb20iLCJzdGF0ZSI6InByb3ZlbiIsImxvYyI6ImVuIiwidHoiOjAsInR5cGUiOiJlbWFpbCIsImlzcyI6ImNvbS5saWZlLmF1cmEifQ.ijaHDiNRLUIKdkziVB-zt8DA8WNH7RNwvYkp2EGDxTM"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getApng() {
|
async function getApng() {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
@ -125,6 +138,9 @@ function App(): JSX.Element {
|
|||||||
element={<SubscriptionPage />}
|
element={<SubscriptionPage />}
|
||||||
/>
|
/>
|
||||||
<Route path={routes.client.paymentMethod()} element={<PaymentPage />} />
|
<Route path={routes.client.paymentMethod()} element={<PaymentPage />} />
|
||||||
|
<Route path={routes.client.paymentResult()} element={<PaymentResultPage />} />
|
||||||
|
<Route path={routes.client.paymentSuccess()} element={<PaymentSuccessPage />} />
|
||||||
|
<Route path={routes.client.paymentFail()} element={<PaymentFailPage />} />
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.wallpaper()}
|
path={routes.client.wallpaper()}
|
||||||
element={<ProtectWallpaperPage />}
|
element={<ProtectWallpaperPage />}
|
||||||
|
|||||||
@ -67,8 +67,8 @@ function BreathPage({ leoApng }: BreathPageProps): JSX.Element {
|
|||||||
setIsOpenModal(false);
|
setIsOpenModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const token =
|
|
||||||
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
|
const token = useSelector(selectors.selectToken)
|
||||||
const createCallback = useCallback(async () => {
|
const createCallback = useCallback(async () => {
|
||||||
const data: UserCallbacks.PayloadPost = {
|
const data: UserCallbacks.PayloadPost = {
|
||||||
data: {
|
data: {
|
||||||
@ -100,7 +100,7 @@ function BreathPage({ leoApng }: BreathPageProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return createCallbackRequest.user_callback;
|
return createCallbackRequest.user_callback;
|
||||||
}, [api, dispatch]);
|
}, [api, dispatch, token]);
|
||||||
|
|
||||||
useApiCall<UserCallbacks.IUserCallbacks>(createCallback);
|
useApiCall<UserCallbacks.IUserCallbacks>(createCallback);
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,7 @@ import FullScreenModal from "../FullScreenModal";
|
|||||||
import CompatibilityLoading from "../CompatibilityLoading";
|
import CompatibilityLoading from "../CompatibilityLoading";
|
||||||
|
|
||||||
function CompatResultPage(): JSX.Element {
|
function CompatResultPage(): JSX.Element {
|
||||||
const token =
|
const token = useSelector(selectors.selectToken)
|
||||||
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
@ -82,7 +81,7 @@ function CompatResultPage(): JSX.Element {
|
|||||||
setText(aICompat?.compat?.body || "Loading...");
|
setText(aICompat?.compat?.body || "Loading...");
|
||||||
|
|
||||||
return aICompat.compat;
|
return aICompat.compat;
|
||||||
}, [api, rightUser, categoryId, birthdate]);
|
}, [api, rightUser, categoryId, birthdate, token]);
|
||||||
|
|
||||||
useApiCall<AICompats.ICompat | AIRequests.IAiRequest>(loadData);
|
useApiCall<AICompats.ICompat | AIRequests.IAiRequest>(loadData);
|
||||||
|
|
||||||
|
|||||||
@ -34,8 +34,7 @@ const buttonTextFormatter = (text: string): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function HomePage(): JSX.Element {
|
function HomePage(): JSX.Element {
|
||||||
const token =
|
const token = useSelector(selectors.selectToken)
|
||||||
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|||||||
@ -23,20 +23,15 @@ export default function CheckoutForm() {
|
|||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
|
||||||
const { error, paymentIntent } = await stripe.confirmPayment({
|
const { error } = await stripe.confirmPayment({
|
||||||
elements,
|
elements,
|
||||||
confirmParams: {
|
confirmParams: {
|
||||||
return_url: window.location.href,
|
return_url: `https://${window.location.host}/payment/result`,
|
||||||
},
|
}
|
||||||
redirect: "if_required",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setMessage(error?.message || "Oops! Something went wrong.");
|
setMessage(error?.message || "Oops! Something went wrong.");
|
||||||
} else if (paymentIntent && paymentIntent.status === "succeeded") {
|
|
||||||
setMessage("Payment succeeded!");
|
|
||||||
} else {
|
|
||||||
setMessage("Unexpected state");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { Stripe, loadStripe } from "@stripe/stripe-js";
|
import { Stripe, loadStripe } from "@stripe/stripe-js";
|
||||||
import { Elements } from "@stripe/react-stripe-js";
|
import { Elements } from "@stripe/react-stripe-js";
|
||||||
import CheckoutForm from "./CheckoutForm";
|
import CheckoutForm from "./CheckoutForm";
|
||||||
|
import { useAuth } from "@/auth";
|
||||||
|
|
||||||
interface StripeModalProps {
|
interface StripeModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -16,14 +17,15 @@ interface StripeModalProps {
|
|||||||
export function StripeModal({
|
export function StripeModal({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
// onSuccess,
|
}: // onSuccess,
|
||||||
// onError,
|
// onError,
|
||||||
}: StripeModalProps): JSX.Element {
|
StripeModalProps): JSX.Element {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
const { token } = useAuth();
|
||||||
const [stripePromise, setStripePromise] =
|
const [stripePromise, setStripePromise] =
|
||||||
useState<Promise<Stripe | null> | null>(null);
|
useState<Promise<Stripe | null> | null>(null);
|
||||||
const [clientSecret, setClientSecret] = useState<string>("");
|
const [clientSecret, setClientSecret] = useState<string>("");
|
||||||
const isLoading = false;
|
const [isLoading, setIsLoading ] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -33,25 +35,18 @@ export function StripeModal({
|
|||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("https://aura.wit.life/api/v1/user/subscription_receipts.json", {
|
if (!open) return;
|
||||||
method: "POST",
|
(async () => {
|
||||||
headers: {
|
|
||||||
Authorization:
|
const { subscription_receipt } = await api.createSubscriptionReceipt({
|
||||||
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw",
|
token,
|
||||||
"Content-Type": "application/json",
|
itemInterval: "year",
|
||||||
},
|
});
|
||||||
body: JSON.stringify({
|
|
||||||
way: "stripe",
|
|
||||||
subscription_receipt: {
|
|
||||||
item_interval: "year",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}).then(async (res) => {
|
|
||||||
const { subscription_receipt } = await res.json();
|
|
||||||
const { client_secret } = subscription_receipt.data;
|
const { client_secret } = subscription_receipt.data;
|
||||||
setClientSecret(client_secret);
|
setClientSecret(client_secret);
|
||||||
});
|
setIsLoading(false);
|
||||||
}, []);
|
})();
|
||||||
|
}, [api, token, open]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
30
src/components/PaymentPage/results/ErrorPage/index.tsx
Normal file
30
src/components/PaymentPage/results/ErrorPage/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
|
||||||
|
function PaymentFailPage(): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleNext = () => navigate(routes.client.paymentMethod());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<img src="/ExclamationIcon.png" alt="Exclamation Icon" />
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Title variant="h1">{t("auweb.pay_bad.title")}</Title>
|
||||||
|
<p className={styles.list}>{t("auweb.pay_bad.text1")}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottom}>
|
||||||
|
<p className={styles.description}>{t("auweb.pay_bad.text2")}</p>
|
||||||
|
<MainButton className={styles.button} onClick={handleNext}>
|
||||||
|
{t("auweb.pay_bad.button")}
|
||||||
|
</MainButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentFailPage;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
/* max-height: -webkit-fill-available; */
|
||||||
|
overflow-y: scroll;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #fe2b57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
25
src/components/PaymentPage/results/SuccessPage/index.tsx
Normal file
25
src/components/PaymentPage/results/SuccessPage/index.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import routes from '@/routes'
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
|
||||||
|
function PaymentSuccessPage(): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const handleNext = () => navigate(routes.client.home())
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<img src="/SuccessIcon.png" alt="Success Icon" />
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Title variant="h1">{t("auweb.pay_good.title")}</Title>
|
||||||
|
<p>{t("auweb.pay_good.text1")}</p>
|
||||||
|
</div>
|
||||||
|
<MainButton className={styles.button} onClick={handleNext}>{t("auweb.pay_good.button")}</MainButton>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentSuccessPage;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
flex: auto;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
max-height: -webkit-fill-available;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text > p {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #FE2B57;
|
||||||
|
}
|
||||||
21
src/components/PaymentPage/results/index.tsx
Normal file
21
src/components/PaymentPage/results/index.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
function PaymentResultPage(): JSX.Element {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const status = searchParams.get("redirect_status");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "succeeded") {
|
||||||
|
return navigate(routes.client.paymentSuccess());
|
||||||
|
}
|
||||||
|
return navigate(routes.client.paymentFail());
|
||||||
|
}, [navigate, status]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentResultPage;
|
||||||
@ -58,8 +58,7 @@ function WallpaperPage(): JSX.Element {
|
|||||||
const api = useApi();
|
const api = useApi();
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const locale = i18n.language;
|
const locale = i18n.language;
|
||||||
const token =
|
const token = useSelector(selectors.selectToken)
|
||||||
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
|
|||||||
@ -17,6 +17,9 @@ const routes = {
|
|||||||
attention: () => [host, "attention"].join("/"),
|
attention: () => [host, "attention"].join("/"),
|
||||||
feedback: () => [host, "feedback"].join("/"),
|
feedback: () => [host, "feedback"].join("/"),
|
||||||
paymentMethod: () => [host, "payment", "method"].join("/"),
|
paymentMethod: () => [host, "payment", "method"].join("/"),
|
||||||
|
paymentResult: () => [host, "payment", "result"].join("/"),
|
||||||
|
paymentSuccess: () => [host, "payment", "success"].join("/"),
|
||||||
|
paymentFail: () => [host, "payment", "fail"].join("/"),
|
||||||
wallpaper: () => [host, "wallpaper"].join("/"),
|
wallpaper: () => [host, "wallpaper"].join("/"),
|
||||||
static: () => [host, "static", ":typeId"].join("/"),
|
static: () => [host, "static", ":typeId"].join("/"),
|
||||||
legal: (type: string) => [host, "static", type].join("/"),
|
legal: (type: string) => [host, "static", type].join("/"),
|
||||||
@ -115,6 +118,9 @@ export const withoutFooterRoutes = [
|
|||||||
routes.client.compatibilityResult(),
|
routes.client.compatibilityResult(),
|
||||||
routes.client.home(),
|
routes.client.home(),
|
||||||
routes.client.breathResult(),
|
routes.client.breathResult(),
|
||||||
|
routes.client.paymentResult(),
|
||||||
|
routes.client.paymentSuccess(),
|
||||||
|
routes.client.paymentFail(),
|
||||||
];
|
];
|
||||||
export const hasNoFooter = (path: string) =>
|
export const hasNoFooter = (path: string) =>
|
||||||
!withoutFooterRoutes.includes(path);
|
!withoutFooterRoutes.includes(path);
|
||||||
@ -134,6 +140,9 @@ export const withoutHeaderRoutes = [
|
|||||||
routes.client.compatibility(),
|
routes.client.compatibility(),
|
||||||
routes.client.subscription(),
|
routes.client.subscription(),
|
||||||
routes.client.paymentMethod(),
|
routes.client.paymentMethod(),
|
||||||
|
routes.client.paymentResult(),
|
||||||
|
routes.client.paymentSuccess(),
|
||||||
|
routes.client.paymentFail(),
|
||||||
];
|
];
|
||||||
export const hasNoHeader = (path: string) =>
|
export const hasNoHeader = (path: string) =>
|
||||||
!withoutHeaderRoutes.includes(path);
|
!withoutHeaderRoutes.includes(path);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user