mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-13 07:55:06 +01:00
Language dropdown, language in settings, add temporary confirmation to delete account
This commit is contained in:
parent
54cd1d52ca
commit
2b23353e40
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -4,5 +4,8 @@
|
||||
"eslint.format.enable": true,
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
}
|
||||
}
|
||||
}
|
1
public/skull.svg
Normal file
1
public/skull.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M27.865 16.751c0-6.242-4.411-9.988-9.927-9.988s-9.835 3.746-9.835 9.988c0 3.48-.103 6.485 3.897 7.89v2.722c0 1.034.966 1.872 2 1.872 1.035 0 2-.838 2-1.872v-1.97 1.97c0 1.034.965 1.872 2 1.872 1.036 0 2-.838 2-1.872v-1.97 1.97c0 1.034.966 1.872 2 1.872s2-.838 2-1.872v-2.722c4-1.405 3.865-4.41 3.865-7.89z"/><circle fill="#292F33" cx="13.629" cy="15.503" r="3.121"/><path fill="#292F33" d="M25.488 15.503c0 1.724 0 3.121-3.121 3.121-3.12 0-3.12-1.397-3.12-3.121s1.396-3.121 3.12-3.121c1.725 0 3.121 1.397 3.121 3.121zm-6.301 5.656c-.157-.382-.626-.662-1.189-.662-.561 0-1.031.28-1.188.662-.394.11-.685.469-.685.898 0 .517.419.936.937.936.409 0 .753-.263.88-.628.019 0 .037.004.056.004.019 0 .037-.004.057-.004.128.365.472.628.88.628.517 0 .936-.419.936-.936 0-.429-.291-.786-.684-.898z"/><path d="M11 27c0-.367.075-.713.195-1.038-.984-.447-1.831-1.082-2.503-1.97-1.107.969-2.163 1.876-3.127 2.695C4.985 26.26 4.275 26 3.5 26 1.567 26 0 27.566 0 29.5c0 1.778 1.33 3.229 3.046 3.454C3.271 34.671 4.722 36 6.5 36c1.933 0 3.5-1.566 3.5-3.5 0-.775-.26-1.485-.686-2.065.6-.706 1.246-1.46 1.931-2.25C11.088 27.821 11 27.421 11 27zm16.872-15.482c.884-.769 1.729-1.495 2.515-2.163.569.403 1.262.645 2.013.645 1.934 0 3.5-1.567 3.5-3.5 0-1.743-1.277-3.177-2.945-3.444C32.735 1.335 31.281 0 29.5 0 27.566 0 26 1.567 26 3.5c0 .775.26 1.485.687 2.065-.594.7-1.233 1.445-1.911 2.227 1.3.871 2.361 2.095 3.096 3.726zM3.5 10c.775 0 1.485-.26 2.065-.687.799.679 1.661 1.419 2.564 2.204.735-1.631 1.795-2.855 3.096-3.726-.679-.781-1.317-1.527-1.912-2.226.427-.58.687-1.29.687-2.065C10 1.567 8.433 0 6.5 0 4.722 0 3.271 1.33 3.046 3.046 1.33 3.271 0 4.722 0 6.5 0 8.433 1.567 10 3.5 10zm28.9 16c-.752 0-1.444.242-2.014.645-.952-.809-1.99-1.701-3.079-2.653-.672.889-1.519 1.523-2.503 1.971.121.324.196.67.196 1.037 0 .421-.088.821-.245 1.185.685.79 1.331 1.544 1.931 2.25-.426.58-.686 1.29-.686 2.065 0 1.934 1.566 3.5 3.5 3.5 1.781 0 3.235-1.334 3.455-3.056 1.668-.267 2.945-1.701 2.945-3.444 0-1.934-1.566-3.5-3.5-3.5z" fill="#AAB8C2"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -6,6 +6,7 @@ import { Icon, Icons } from "@/components/Icon";
|
||||
export interface OptionItem {
|
||||
id: string;
|
||||
name: string;
|
||||
leftIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
@ -20,12 +21,17 @@ export function Dropdown(props: DropdownProps) {
|
||||
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-denim-500 py-2 pl-3 pr-10 text-left text-white shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-bink-500 focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-bink-300 sm:text-sm">
|
||||
<span className="block truncate">{props.selectedItem.name}</span>
|
||||
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-bink-500 focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-bink-300">
|
||||
<span className="flex gap-4 items-center truncate">
|
||||
{props.selectedItem.leftIcon
|
||||
? props.selectedItem.leftIcon
|
||||
: null}
|
||||
{props.selectedItem.name}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<Icon
|
||||
icon={Icons.CHEVRON_DOWN}
|
||||
className={`transform transition-transform ${
|
||||
className={`transform transition-transform text-xl ${
|
||||
open ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
@ -37,17 +43,18 @@ export function Dropdown(props: DropdownProps) {
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute left-0 right-0 top-10 z-10 mt-1 max-h-60 overflow-auto rounded-md bg-denim-500 py-1 text-white shadow-lg ring-1 ring-black ring-opacity-5 scrollbar-thin scrollbar-track-denim-400 scrollbar-thumb-denim-200 focus:outline-none sm:top-10 sm:text-sm">
|
||||
<Listbox.Options className="absolute left-0 right-0 top-10 z-[1] mt-4 max-h-60 overflow-auto rounded-md bg-dropdown-background py-1 text-white shadow-lg ring-1 ring-black ring-opacity-5 scrollbar-thin scrollbar-track-denim-400 scrollbar-thumb-denim-200 focus:outline-none sm:top-10">
|
||||
{props.options.map((opt) => (
|
||||
<Listbox.Option
|
||||
className={({ active }) =>
|
||||
`relative cursor-default select-none py-2 pl-10 pr-4 ${
|
||||
`flex gap-4 items-center relative cursor-default select-none py-3 pl-4 pr-4 ${
|
||||
active ? "bg-denim-400 text-bink-700" : "text-white"
|
||||
}`
|
||||
}
|
||||
key={opt.id}
|
||||
value={opt}
|
||||
>
|
||||
{opt.leftIcon ? opt.leftIcon : null}
|
||||
{opt.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
|
@ -6,11 +6,40 @@ export interface FlagIconProps {
|
||||
}
|
||||
|
||||
export function FlagIcon(props: FlagIconProps) {
|
||||
// Country code overrides
|
||||
const countryOverrides: Record<string, string> = {
|
||||
en: "gb",
|
||||
cs: "cz",
|
||||
el: "gr",
|
||||
fa: "ir",
|
||||
ko: "kr",
|
||||
he: "il",
|
||||
ze: "cn",
|
||||
ar: "sa",
|
||||
ja: "jp",
|
||||
bs: "ba",
|
||||
vi: "vn",
|
||||
zh: "cn",
|
||||
sl: "si",
|
||||
};
|
||||
|
||||
let countryCode =
|
||||
(props.countryCode || "")?.split("-").pop()?.toLowerCase() || "";
|
||||
if (countryOverrides[countryCode])
|
||||
countryCode = countryOverrides[countryCode];
|
||||
|
||||
if (countryCode === "pirate")
|
||||
return (
|
||||
<div className="w-8 h-6 rounded bg-[#2E3439] flex justify-center items-center">
|
||||
<img src="/skull.svg" className="w-4 h-4" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
"!w-8 h-6 rounded overflow-hidden bg-video-context-flagBg bg-cover bg-center block fi",
|
||||
props.countryCode ? `fi-${props.countryCode}` : undefined
|
||||
props.countryCode ? `fi-${countryCode}` : undefined
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ import { getLanguageFromIETF } from "@/components/player/utils/language";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { useSubtitleStore } from "@/stores/subtitles";
|
||||
import { sortLangCodes } from "@/utils/sortLangCodes";
|
||||
|
||||
export function CaptionOption(props: {
|
||||
countryCode?: string;
|
||||
@ -22,24 +23,6 @@ export function CaptionOption(props: {
|
||||
onClick?: () => void;
|
||||
error?: React.ReactNode;
|
||||
}) {
|
||||
// Country code overrides
|
||||
const countryOverrides: Record<string, string> = {
|
||||
en: "gb",
|
||||
cs: "cz",
|
||||
el: "gr",
|
||||
fa: "ir",
|
||||
ko: "kr",
|
||||
he: "il",
|
||||
ze: "cn",
|
||||
ar: "sa",
|
||||
ja: "jp",
|
||||
bs: "ba",
|
||||
};
|
||||
let countryCode =
|
||||
(props.countryCode || "")?.split("-").pop()?.toLowerCase() || "";
|
||||
if (countryOverrides[countryCode])
|
||||
countryCode = countryOverrides[countryCode];
|
||||
|
||||
return (
|
||||
<SelectableLink
|
||||
selected={props.selected}
|
||||
@ -52,7 +35,7 @@ export function CaptionOption(props: {
|
||||
className="flex items-center"
|
||||
>
|
||||
<span data-code={props.countryCode} className="mr-3">
|
||||
<FlagIcon countryCode={countryCode} />
|
||||
<FlagIcon countryCode={props.countryCode} />
|
||||
</span>
|
||||
<span>{props.children}</span>
|
||||
</span>
|
||||
@ -64,19 +47,12 @@ function searchSubs(
|
||||
subs: (SubtitleSearchItem & { languageName: string })[],
|
||||
searchQuery: string
|
||||
) {
|
||||
const languagesOrder = ["en", "hi", "fr", "de", "nl", "pt"].reverse(); // Reverse is neccesary, not sure why
|
||||
|
||||
const sorted = sortLangCodes(subs.map((t) => t.attributes.language));
|
||||
let results = subs.sort((a, b) => {
|
||||
if (
|
||||
languagesOrder.indexOf(b.attributes.language) !== -1 ||
|
||||
languagesOrder.indexOf(a.attributes.language) !== -1
|
||||
)
|
||||
return (
|
||||
languagesOrder.indexOf(b.attributes.language) -
|
||||
languagesOrder.indexOf(a.attributes.language)
|
||||
);
|
||||
|
||||
return a.languageName.localeCompare(b.languageName);
|
||||
return (
|
||||
sorted.indexOf(a.attributes.language) -
|
||||
sorted.indexOf(b.attributes.language)
|
||||
);
|
||||
});
|
||||
|
||||
if (searchQuery.trim().length > 0) {
|
||||
@ -152,7 +128,7 @@ export function CaptionsView({ id }: { id: string }) {
|
||||
if (req.loading) content = <p>loading...</p>;
|
||||
else if (req.error) content = <p>errored!</p>;
|
||||
else if (req.value) {
|
||||
const subs = req.value.map((v) => {
|
||||
const subs = req.value.filter(Boolean).map((v) => {
|
||||
const languageName =
|
||||
getLanguageFromIETF(v.attributes.language) ?? "unknown";
|
||||
return {
|
||||
|
@ -18,6 +18,7 @@ import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||
import { useThemeStore } from "@/stores/theme";
|
||||
|
||||
import { SubPageLayout } from "./layouts/SubPageLayout";
|
||||
import { LocalePart } from "./settings/LocalePart";
|
||||
|
||||
function SettingsLayout(props: { children: React.ReactNode }) {
|
||||
const { isMobile } = useIsMobile();
|
||||
@ -79,6 +80,9 @@ export function SettingsPage() {
|
||||
<RegisterCalloutPart />
|
||||
)}
|
||||
</div>
|
||||
<div id="settings-locale">
|
||||
<LocalePart />
|
||||
</div>
|
||||
<div id="settings-appearance">
|
||||
<ThemePart active={activeTheme} setTheme={setTheme} />
|
||||
</div>
|
||||
|
@ -14,6 +14,8 @@ export function AccountActionsPart() {
|
||||
const { logout } = useAuthData();
|
||||
const [deleteResult, deleteExec] = useAsyncFn(async () => {
|
||||
if (!account) return;
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (!confirm("You sure bro?")) return;
|
||||
await deleteUser(url, account);
|
||||
logout();
|
||||
}, [logout, account, url]);
|
||||
|
36
src/pages/settings/LocalePart.tsx
Normal file
36
src/pages/settings/LocalePart.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Dropdown } from "@/components/Dropdown";
|
||||
import { FlagIcon } from "@/components/FlagIcon";
|
||||
import { Heading1 } from "@/components/utils/Text";
|
||||
import { appLanguageOptions } from "@/setup/i18n";
|
||||
import { useLanguageStore } from "@/stores/language";
|
||||
import { sortLangCodes } from "@/utils/sortLangCodes";
|
||||
|
||||
export function LocalePart() {
|
||||
const sorted = sortLangCodes(appLanguageOptions.map((t) => t.id));
|
||||
const { language, setLanguage } = useLanguageStore();
|
||||
|
||||
const options = appLanguageOptions
|
||||
.sort((a, b) => sorted.indexOf(a.id) - sorted.indexOf(b.id))
|
||||
.map((opt) => ({
|
||||
id: opt.id,
|
||||
name: `${opt.englishName} — ${opt.nativeName}`,
|
||||
leftIcon: <FlagIcon countryCode={opt.id} />,
|
||||
}));
|
||||
|
||||
const selected = options.find((t) => t.id === language);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Heading1 border>Locale</Heading1>
|
||||
<p className="text-white font-bold mb-3">Application language</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
Language applied to the entire application.
|
||||
</p>
|
||||
<Dropdown
|
||||
options={options}
|
||||
selectedItem={selected || options[0]}
|
||||
setSelectedItem={(opt) => setLanguage(opt.id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -18,6 +18,7 @@ export function SidebarPart() {
|
||||
|
||||
const settingLinks = [
|
||||
{ text: "Account", id: "settings-account", icon: Icons.USER },
|
||||
{ text: "Locale", id: "settings-locale", icon: Icons.LINK },
|
||||
{ text: "Appearance", id: "settings-appearance", icon: Icons.GITHUB },
|
||||
{ text: "Captions", id: "settings-captions", icon: Icons.CAPTIONS },
|
||||
];
|
||||
@ -35,10 +36,10 @@ export function SidebarPart() {
|
||||
|
||||
const visible = !(
|
||||
Math.floor(
|
||||
100 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100
|
||||
50 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100
|
||||
) < percentageVisible ||
|
||||
Math.floor(
|
||||
100 - ((rect.bottom - windowHeight) / rect.height) * 100
|
||||
50 - ((rect.bottom - windowHeight) / rect.height) * 100
|
||||
) < percentageVisible
|
||||
);
|
||||
|
||||
@ -80,6 +81,7 @@ export function SidebarPart() {
|
||||
icon={v.icon}
|
||||
active={v.id === activeLink}
|
||||
onClick={() => scrollTo(v.id)}
|
||||
key={v.id}
|
||||
>
|
||||
{v.text}
|
||||
</SidebarLink>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
|
||||
import i18n from "@/setup/i18n";
|
||||
@ -10,14 +11,17 @@ export interface LanguageStore {
|
||||
}
|
||||
|
||||
export const useLanguageStore = create(
|
||||
immer<LanguageStore>((set) => ({
|
||||
language: "en",
|
||||
setLanguage(v) {
|
||||
set((s) => {
|
||||
s.language = v;
|
||||
});
|
||||
},
|
||||
}))
|
||||
persist(
|
||||
immer<LanguageStore>((set) => ({
|
||||
language: "en",
|
||||
setLanguage(v) {
|
||||
set((s) => {
|
||||
s.language = v;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{ name: "__MW::locale" }
|
||||
)
|
||||
);
|
||||
|
||||
export function useLanguageListener() {
|
||||
|
12
src/utils/sortLangCodes.ts
Normal file
12
src/utils/sortLangCodes.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export function sortLangCodes(langCodes: string[]) {
|
||||
const languagesOrder = ["en", "hi", "fr", "de", "nl", "pt"].reverse(); // Reverse is neccesary, not sure why
|
||||
|
||||
const results = langCodes.sort((a, b) => {
|
||||
if (languagesOrder.indexOf(b) !== -1 || languagesOrder.indexOf(a) !== -1)
|
||||
return languagesOrder.indexOf(b) - languagesOrder.indexOf(a);
|
||||
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
@ -4,23 +4,23 @@ export const defaultTheme = {
|
||||
themePreview: {
|
||||
primary: "#505DBD",
|
||||
secondary: "#73739D",
|
||||
ghost: "white"
|
||||
ghost: "white",
|
||||
},
|
||||
|
||||
// Branding
|
||||
pill: {
|
||||
background: "#1C1C36"
|
||||
background: "#1C1C36",
|
||||
},
|
||||
|
||||
// meta data for the theme itself
|
||||
global: {
|
||||
accentA: "#505DBD",
|
||||
accentB: "#3440A1"
|
||||
accentB: "#3440A1",
|
||||
},
|
||||
|
||||
// light bar
|
||||
lightBar: {
|
||||
light: "#2A2A71"
|
||||
light: "#2A2A71",
|
||||
},
|
||||
|
||||
// Buttons
|
||||
@ -39,14 +39,14 @@ export const defaultTheme = {
|
||||
purple: "#6b298a",
|
||||
purpleHover: "#7f35a1",
|
||||
cancel: "#252533",
|
||||
cancelHover: "#3C3C4A"
|
||||
cancelHover: "#3C3C4A",
|
||||
},
|
||||
|
||||
// only used for body colors/textures
|
||||
background: {
|
||||
main: "#0A0A10",
|
||||
accentA: "#6E3B80",
|
||||
accentB: "#1F1F50"
|
||||
accentB: "#1F1F50",
|
||||
},
|
||||
|
||||
// typography
|
||||
@ -55,7 +55,7 @@ export const defaultTheme = {
|
||||
text: "#73739D",
|
||||
dimmed: "#926CAD",
|
||||
divider: "#262632",
|
||||
secondary: "#64647B"
|
||||
secondary: "#64647B",
|
||||
},
|
||||
|
||||
// search bar
|
||||
@ -64,7 +64,7 @@ export const defaultTheme = {
|
||||
focused: "#24243C",
|
||||
placeholder: "#4A4A71",
|
||||
icon: "#545476",
|
||||
text: "#FFFFFF"
|
||||
text: "#FFFFFF",
|
||||
},
|
||||
|
||||
// media cards
|
||||
@ -76,13 +76,18 @@ export const defaultTheme = {
|
||||
barColor: "#4B4B63",
|
||||
barFillColor: "#BA7FD6",
|
||||
badge: "#151522",
|
||||
badgeText: "#5F5F7A"
|
||||
badgeText: "#5F5F7A",
|
||||
},
|
||||
|
||||
// Large card
|
||||
largeCard: {
|
||||
background: "#171728",
|
||||
icon: "#6741A5"
|
||||
icon: "#6741A5",
|
||||
},
|
||||
|
||||
// Dropdown
|
||||
dropdown: {
|
||||
background: "#171728",
|
||||
},
|
||||
|
||||
// Passphrase
|
||||
@ -92,7 +97,7 @@ export const defaultTheme = {
|
||||
wordBackground: "#171728",
|
||||
copyText: "#58587A",
|
||||
copyTextHover: "#8888AA",
|
||||
errorText: "#DB3D62"
|
||||
errorText: "#DB3D62",
|
||||
},
|
||||
|
||||
// Settings page
|
||||
@ -105,19 +110,19 @@ export const defaultTheme = {
|
||||
inactive: "#8D68A9",
|
||||
icon: "#926CAD",
|
||||
iconActivated: "#6942A8",
|
||||
activated: "#CBA1E8"
|
||||
}
|
||||
activated: "#CBA1E8",
|
||||
},
|
||||
},
|
||||
|
||||
card: {
|
||||
border: "#2A243E",
|
||||
background: "#29243D",
|
||||
altBackground: "#29243D"
|
||||
}
|
||||
altBackground: "#29243D",
|
||||
},
|
||||
},
|
||||
|
||||
utils: {
|
||||
divider: "#353549"
|
||||
divider: "#353549",
|
||||
},
|
||||
|
||||
// Error page
|
||||
@ -126,20 +131,20 @@ export const defaultTheme = {
|
||||
border: "#252534",
|
||||
|
||||
type: {
|
||||
secondary: "#62627D"
|
||||
}
|
||||
secondary: "#62627D",
|
||||
},
|
||||
},
|
||||
|
||||
// About page
|
||||
about: {
|
||||
circle: "#262632",
|
||||
circleText: "#9A9AC3"
|
||||
circleText: "#9A9AC3",
|
||||
},
|
||||
|
||||
progress: {
|
||||
background: "#8787A8",
|
||||
preloaded: "#8787A8",
|
||||
filled: "#A75FC9"
|
||||
filled: "#A75FC9",
|
||||
},
|
||||
|
||||
// video player
|
||||
@ -151,11 +156,11 @@ export const defaultTheme = {
|
||||
error: "#E44F4F",
|
||||
success: "#40B44B",
|
||||
loading: "#B759D8",
|
||||
noresult: "#64647B"
|
||||
noresult: "#64647B",
|
||||
},
|
||||
|
||||
audio: {
|
||||
set: "#A75FC9"
|
||||
set: "#A75FC9",
|
||||
},
|
||||
|
||||
context: {
|
||||
@ -175,16 +180,16 @@ export const defaultTheme = {
|
||||
|
||||
buttons: {
|
||||
list: "#161C26",
|
||||
active: "#0D1317"
|
||||
active: "#0D1317",
|
||||
},
|
||||
|
||||
type: {
|
||||
main: "#617A8A",
|
||||
secondary: "#374A56",
|
||||
accent: "#A570FA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
accent: "#A570FA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user