Added currency selection and auto-convertion

This commit is contained in:
DrMint 2022-03-06 18:23:10 +01:00
parent 250adfc746
commit f5c661c623
11 changed files with 241 additions and 153 deletions

View File

@ -13,7 +13,8 @@ import ReactTooltip from "react-tooltip";
import { useAppLayout } from "contexts/AppLayoutContext";
import { ImageQuality } from "./Img";
import Popup from "./Popup";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import Select from "./Select";
type AppLayoutProps = {
subPanel?: React.ReactNode;
@ -96,7 +97,20 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
document.getElementsByTagName("html")[0].style.fontSize = `${
(appLayout.fontSize || 1) * 100
}%`;
});
}, [appLayout.fontSize]);
const currencyOptions = ["EUR", "USD", "CAD", "JPY"];
const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => {
appLayout.currency &&
setCurrencySelect(currencyOptions.indexOf(appLayout.currency));
}, [appLayout.currency]);
useEffect(() => {
currencySelect >= 0 &&
appLayout.setCurrency(currencyOptions[currencySelect]);
}, [currencySelect]);
return (
<div
@ -268,93 +282,115 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
>
<h2 className="text-2xl">Settings</h2>
<h3 className="text-xl mt-4">Theme</h3>
<div className="flex flex-row">
<Button
onClick={() => {
appLayout.setDarkMode(false);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === false
}
className="rounded-r-none"
>
Light
</Button>
<Button
onClick={() => {
appLayout.setSelectedThemeMode(false);
}}
active={appLayout.selectedThemeMode === false}
className="rounded-l-none rounded-r-none border-x-0"
>
Auto
</Button>
<Button
onClick={() => {
appLayout.setDarkMode(true);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === true
}
className="rounded-l-none"
>
Dark
</Button>
</div>
<div className="mt-4 grid gap-8 place-items-center text-center desktop:grid-cols-2">
<div>
<h3 className="text-xl">Theme</h3>
<div className="flex flex-row">
<Button
onClick={() => {
appLayout.setDarkMode(false);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === false
}
className="rounded-r-none"
>
Light
</Button>
<Button
onClick={() => {
appLayout.setSelectedThemeMode(false);
}}
active={appLayout.selectedThemeMode === false}
className="rounded-l-none rounded-r-none border-x-0"
>
Auto
</Button>
<Button
onClick={() => {
appLayout.setDarkMode(true);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === true
}
className="rounded-l-none"
>
Dark
</Button>
</div>
</div>
<h3 className="text-xl mt-4">Font size</h3>
<div className="flex flex-row">
<Button
className="rounded-r-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize / 1.05 : 1 / 1.05
)
}
>
<span className="material-icons">text_decrease</span>
</Button>
<Button
className="rounded-l-none rounded-r-none border-x-0"
onClick={() => appLayout.setFontSize(1)}
>
{((appLayout.fontSize || 1) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}
%
</Button>
<Button
className="rounded-l-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize * 1.05 : 1 * 1.05
)
}
>
<span className="material-icons">text_increase</span>
</Button>
</div>
<div>
<h3 className="text-xl">Currency</h3>
<div>
<Select
options={currencyOptions}
state={currencySelect}
setState={setCurrencySelect}
className="w-28"
/>
</div>
</div>
<h3 className="text-xl mt-4">Font</h3>
<Button
active={appLayout.dyslexic === false}
onClick={() => appLayout.setDyslexic(false)}
className="font-zenMaruGothic"
>
Zen Maru Gothic
</Button>
<Button
active={appLayout.dyslexic === true}
onClick={() => appLayout.setDyslexic(true)}
className="font-openDyslexic"
>
OpenDyslexic
</Button>
<div>
<h3 className="text-xl">Font size</h3>
<div className="flex flex-row">
<Button
className="rounded-r-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize / 1.05 : 1 / 1.05
)
}
>
<span className="material-icons">text_decrease</span>
</Button>
<Button
className="rounded-l-none rounded-r-none border-x-0"
onClick={() => appLayout.setFontSize(1)}
>
{((appLayout.fontSize || 1) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}
%
</Button>
<Button
className="rounded-l-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize * 1.05 : 1 * 1.05
)
}
>
<span className="material-icons">text_increase</span>
</Button>
</div>
</div>
<div>
<h3 className="text-xl">Font</h3>
<div className="grid gap-2">
<Button
active={appLayout.dyslexic === false}
onClick={() => appLayout.setDyslexic(false)}
className="font-zenMaruGothic"
>
Zen Maru Gothic
</Button>
<Button
active={appLayout.dyslexic === true}
onClick={() => appLayout.setDyslexic(true)}
className="font-openDyslexic"
>
OpenDyslexic
</Button>
</div>
</div>
</div>
</Popup>
<ReactTooltip

View File

@ -1,8 +1,12 @@
import Link from "next/link";
import { GetLibraryItemsPreviewQuery } from "graphql/operations-types";
import {
GetCurrenciesQuery,
GetLibraryItemsPreviewQuery,
} from "graphql/operations-types";
import { prettyDate, prettyPrice, prettyItemSubType } from "queries/helpers";
import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import { useAppLayout } from "contexts/AppLayoutContext";
export type LibraryItemsPreviewProps = {
className?: string;
@ -15,12 +19,14 @@ export type LibraryItemsPreviewProps = {
release_date?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"];
metadata?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"];
};
currencies?: GetCurrenciesQuery["currencies"]["data"];
};
export default function LibraryItemsPreview(
props: LibraryItemsPreviewProps
): JSX.Element {
const item = props.item;
const appLayout = useAppLayout();
return (
<Link href={"/library/" + item.slug} passHref>
@ -61,12 +67,16 @@ export default function LibraryItemsPreview(
) : (
""
)}
{item.price ? (
{item.price && props.currencies ? (
<p className="mobile:text-xs text-sm justify-self-end">
<span className="material-icons !text-base translate-y-[.15em] mr-1">
shopping_cart
</span>
{prettyPrice(item.price)}
{prettyPrice(
item.price,
props.currencies,
appLayout.currency
)}
</p>
) : (
""

View File

@ -15,7 +15,7 @@ export default function Select(props: SelectProps): JSX.Element {
return (
<div
className={`relative transition-[filter] ${
className={`relative text-center transition-[filter] ${
opened && "drop-shadow-shade-lg z-10"
} ${props.className}`}
>

View File

@ -12,6 +12,7 @@ export interface AppLayoutState {
selectedThemeMode: boolean | undefined;
fontSize: number | undefined;
dyslexic: boolean | undefined;
currency: string | undefined;
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setLanguagePanelOpen: React.Dispatch<
React.SetStateAction<boolean | undefined>
@ -27,6 +28,7 @@ export interface AppLayoutState {
>;
setFontSize: React.Dispatch<React.SetStateAction<number | undefined>>;
setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>;
}
const initialState: AppLayoutState = {
@ -39,6 +41,7 @@ const initialState: AppLayoutState = {
selectedThemeMode: false,
fontSize: 1,
dyslexic: false,
currency: "USD",
setSubPanelOpen: () => {},
setLanguagePanelOpen: () => {},
setMainPanelReduced: () => {},
@ -48,6 +51,7 @@ const initialState: AppLayoutState = {
setConfigPanelOpen: () => {},
setFontSize: () => {},
setDyslexic: () => {},
setCurrency: () => {},
};
const AppContext = React.createContext<AppLayoutState>(initialState);
@ -96,6 +100,11 @@ export const AppContextProvider = (props: Props) => {
initialState.dyslexic
);
const [currency, setCurrency] = useStateWithLocalStorage<string | undefined>(
"currency",
initialState.currency
);
return (
<AppContext.Provider
value={{
@ -108,6 +117,7 @@ export const AppContextProvider = (props: Props) => {
selectedThemeMode,
fontSize,
dyslexic,
currency,
setSubPanelOpen,
setLanguagePanelOpen,
setConfigPanelOpen,
@ -117,6 +127,7 @@ export const AppContextProvider = (props: Props) => {
setSelectedThemeMode,
setFontSize,
setDyslexic,
setCurrency,
}}
>
{props.children}

View File

@ -174,6 +174,7 @@ query getLibraryItemsPreview($language_code: String) {
attributes {
symbol
code
rate_to_usd
}
}
}
@ -322,6 +323,7 @@ query getLibraryItem($slug: String, $language_code: String) {
attributes {
symbol
code
rate_to_usd
}
}
}
@ -490,6 +492,7 @@ query getLibraryItem($slug: String, $language_code: String) {
attributes {
symbol
code
rate_to_usd
}
}
}
@ -1057,3 +1060,17 @@ query getContentText($slug: String, $language_code: String) {
}
}
}
query getCurrencies {
currencies {
data {
id
attributes {
code
symbol
rate_to_usd
display_decimals
}
}
}
}

View File

@ -286,6 +286,7 @@ export type GetLibraryItemsPreviewQuery = {
__typename: "Currency";
symbol: string;
code: string;
rate_to_usd: number;
};
};
};
@ -478,6 +479,7 @@ export type GetLibraryItemQuery = {
__typename: "Currency";
symbol: string;
code: string;
rate_to_usd: number;
};
};
};
@ -693,6 +695,7 @@ export type GetLibraryItemQuery = {
__typename: "Currency";
symbol: string;
code: string;
rate_to_usd: number;
};
};
};
@ -1416,3 +1419,23 @@ export type GetContentTextQuery = {
}>;
};
};
export type GetCurrenciesQueryVariables = Exact<{ [key: string]: never }>;
export type GetCurrenciesQuery = {
__typename: "Query";
currencies: {
__typename: "CurrencyEntityResponseCollection";
data: Array<{
__typename: "CurrencyEntity";
id: string;
attributes: {
__typename: "Currency";
code: string;
symbol: string;
rate_to_usd: number;
display_decimals: boolean;
};
}>;
};
};

View File

@ -11,6 +11,8 @@ import {
GetContentsSlugsQueryVariables,
GetContentTextQuery,
GetContentTextQueryVariables,
GetCurrenciesQuery,
GetCurrenciesQueryVariables,
GetErasQuery,
GetErasQueryVariables,
GetLibraryItemQuery,
@ -123,3 +125,10 @@ export async function getContentText(
const query = getQueryFromOperations("getContentText");
return await graphQL(query, JSON.stringify(variables));
}
export async function getCurrencies(
variables: GetCurrenciesQueryVariables
): Promise<GetCurrenciesQuery> {
const query = getQueryFromOperations("getCurrencies");
return await graphQL(query, JSON.stringify(variables));
}

View File

@ -1647,6 +1647,7 @@ input CurrencyFiltersInput {
id: IDFilterInput
symbol: StringFilterInput
code: StringFilterInput
rate_to_usd: FloatFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [CurrencyFiltersInput]
@ -1657,11 +1658,13 @@ input CurrencyFiltersInput {
input CurrencyInput {
symbol: String
code: String
rate_to_usd: Float
}
type Currency {
symbol: String!
code: String!
rate_to_usd: Float
createdAt: DateTime
updatedAt: DateTime
}
@ -2099,46 +2102,6 @@ type MetadataTypeEntityResponseCollection {
meta: ResponseCollectionMeta!
}
input OtherSubtypeFiltersInput {
id: IDFilterInput
slug: StringFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [OtherSubtypeFiltersInput]
or: [OtherSubtypeFiltersInput]
not: OtherSubtypeFiltersInput
}
input OtherSubtypeInput {
slug: String
titles: [ComponentTranslationsSimpleTitleInput]
}
type OtherSubtype {
slug: String!
titles(
filters: ComponentTranslationsSimpleTitleFiltersInput
pagination: PaginationArg = {}
sort: [String] = []
): [ComponentTranslationsSimpleTitle]
createdAt: DateTime
updatedAt: DateTime
}
type OtherSubtypeEntity {
id: ID
attributes: OtherSubtype
}
type OtherSubtypeEntityResponse {
data: OtherSubtypeEntity
}
type OtherSubtypeEntityResponseCollection {
data: [OtherSubtypeEntity!]!
meta: ResponseCollectionMeta!
}
input PostFiltersInput {
id: IDFilterInput
authors: RecorderFiltersInput
@ -2655,6 +2618,7 @@ input WebsiteInterfaceFiltersInput {
order_by: StringFilterInput
group_by: StringFilterInput
select_option_sidebar: StringFilterInput
group: StringFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [WebsiteInterfaceFiltersInput]
@ -2742,6 +2706,7 @@ input WebsiteInterfaceInput {
order_by: String
group_by: String
select_option_sidebar: String
group: String
}
type WebsiteInterface {
@ -2824,6 +2789,7 @@ type WebsiteInterface {
order_by: String
group_by: String
select_option_sidebar: String
group: String
createdAt: DateTime
updatedAt: DateTime
}
@ -2997,7 +2963,6 @@ union GenericMorph =
| LibraryItem
| MerchItem
| MetadataType
| OtherSubtype
| Post
| RangedContent
| Recorder
@ -3121,12 +3086,6 @@ type Query {
pagination: PaginationArg = {}
sort: [String] = []
): MetadataTypeEntityResponseCollection
otherSubtype(id: ID): OtherSubtypeEntityResponse
otherSubtypes(
filters: OtherSubtypeFiltersInput
pagination: PaginationArg = {}
sort: [String] = []
): OtherSubtypeEntityResponseCollection
post(id: ID): PostEntityResponse
posts(
filters: PostFiltersInput
@ -3277,12 +3236,6 @@ type Mutation {
data: MetadataTypeInput!
): MetadataTypeEntityResponse
deleteMetadataType(id: ID!): MetadataTypeEntityResponse
createOtherSubtype(data: OtherSubtypeInput!): OtherSubtypeEntityResponse
updateOtherSubtype(
id: ID!
data: OtherSubtypeInput!
): OtherSubtypeEntityResponse
deleteOtherSubtype(id: ID!): OtherSubtypeEntityResponse
createPost(data: PostInput!): PostEntityResponse
updatePost(id: ID!, data: PostInput!): PostEntityResponse
deletePost(id: ID!): PostEntityResponse

View File

@ -3,6 +3,7 @@ import ContentPanel, {
} from "components/Panels/ContentPanel";
import { GetStaticPaths, GetStaticProps } from "next";
import {
getCurrencies,
getLibraryItem,
getLibraryItemsSlugs,
getWebsiteInterface,
@ -10,6 +11,7 @@ import {
import {
Enum_Componentmetadatabooks_Binding_Type,
Enum_Componentmetadatabooks_Page_Order,
GetCurrenciesQuery,
GetLibraryItemQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
@ -20,7 +22,6 @@ import {
prettyItemType,
prettyItemSubType,
prettyPrice,
prettySlug,
prettyTestError,
prettyTestWarning,
sortContent,
@ -32,7 +33,6 @@ import ReturnButton, {
import NavOption from "components/PanelComponents/NavOption";
import Chip from "components/Chip";
import Button from "components/Button";
import HorizontalLine from "components/HorizontalLine";
import AppLayout from "components/AppLayout";
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
import InsetBox from "components/InsetBox";
@ -44,12 +44,14 @@ import ContentTOCLine from "components/Library/ContentTOCLine";
interface LibrarySlugProps {
libraryItem: GetLibraryItemQuery;
langui: GetWebsiteInterfaceQuery;
currencies: GetCurrenciesQuery;
}
export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
useTesting(props);
const item = props.libraryItem.libraryItems.data[0].attributes;
const langui = props.langui.websiteInterfaces.data[0].attributes;
const currencies = props.currencies.currencies.data;
const appLayout = useAppLayout();
const isVariantSet =
@ -226,7 +228,9 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
{item.price ? (
<div className="grid place-items-center">
<h3 className="text-xl">{langui.price}</h3>
<p>{prettyPrice(item.price)}</p>
<p>
{prettyPrice(item.price, currencies, appLayout.currency)}
</p>
</div>
) : (
""
@ -409,6 +413,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
langui: await getWebsiteInterface({
language_code: context.locale,
}),
currencies: await getCurrencies({}),
};
return {
props: props,

View File

@ -4,10 +4,12 @@ import ContentPanel, {
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import {
GetCurrenciesQuery,
GetLibraryItemsPreviewQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import {
getCurrencies,
getLibraryItemsPreview,
getWebsiteInterface,
} from "graphql/operations";
@ -22,6 +24,7 @@ import Switch from "components/Switch";
type LibraryProps = {
libraryItems: GetLibraryItemsPreviewQuery;
langui: GetWebsiteInterfaceQuery;
currencies: GetCurrenciesQuery;
};
type GroupLibraryItems = Map<
@ -138,7 +141,11 @@ export default function Library(props: LibraryProps): JSX.Element {
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
>
{items.map((item) => (
<LibraryItemsPreview key={item.id} item={item.attributes} />
<LibraryItemsPreview
key={item.id}
item={item.attributes}
currencies={props.currencies.currencies.data}
/>
))}
</div>
</>
@ -166,6 +173,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
langui: await getWebsiteInterface({
language_code: context.locale,
}),
currencies: await getCurrencies({}),
};
return {
props: props,

View File

@ -4,6 +4,7 @@ import {
ImageQuality,
} from "components/Img";
import {
GetCurrenciesQuery,
GetLibraryItemQuery,
GetLibraryItemsPreviewQuery,
GetWebsiteInterfaceQuery,
@ -24,12 +25,27 @@ export function prettyDate(
}
export function prettyPrice(
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"]
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"],
currencies: GetCurrenciesQuery["currencies"]["data"],
targetCurrencyCode?: string
): string {
return (
pricePicker.currency.data.attributes.symbol +
pricePicker.amount.toLocaleString()
);
if (!targetCurrencyCode) return "";
let result = "";
currencies.map((currency) => {
if (currency.attributes.code === targetCurrencyCode) {
let amountInUSD =
pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd;
let amountInTargetCurrency =
amountInUSD / currency.attributes.rate_to_usd;
result =
currency.attributes.symbol +
amountInTargetCurrency.toLocaleString(undefined, {
minimumFractionDigits: currency.attributes.display_decimals ? 2 : 0,
maximumFractionDigits: currency.attributes.display_decimals ? 2 : 0,
});
}
});
return result;
}
export function prettySlug(slug?: string, parentSlug?: string): string {