mirror of
https://github.com/p-stream/p-stream.git
synced 2026-01-11 20:10:32 +00:00
add manual region picker
This commit is contained in:
parent
8262e5ba12
commit
35a90264e6
4 changed files with 74 additions and 7 deletions
|
|
@ -13,9 +13,12 @@ interface DropdownProps {
|
|||
selectedItem: OptionItem;
|
||||
setSelectedItem: (value: OptionItem) => void;
|
||||
options: Array<OptionItem>;
|
||||
direction?: "up" | "down";
|
||||
}
|
||||
|
||||
export function Dropdown(props: DropdownProps) {
|
||||
const { direction = "down" } = props;
|
||||
|
||||
return (
|
||||
<div className="relative my-4 max-w-[25rem]">
|
||||
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
||||
|
|
@ -31,7 +34,7 @@ export function Dropdown(props: DropdownProps) {
|
|||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<Icon
|
||||
icon={Icons.UP_DOWN_ARROW}
|
||||
className="transform transition-transform text-xl text-dropdown-secondary"
|
||||
className={`transform transition-transform text-xl text-dropdown-secondary ${direction === "up" ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
|
@ -41,7 +44,9 @@ export function Dropdown(props: DropdownProps) {
|
|||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute left-0 right-0 top-10 z-[100] 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-background-secondary scrollbar-thumb-type-secondary focus:outline-none sm:top-10">
|
||||
<Listbox.Options
|
||||
className={`absolute left-0 right-0 z-[100] 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-background-secondary scrollbar-thumb-type-secondary focus:outline-none ${direction === "up" ? "bottom-full mb-4" : "top-full"}`}
|
||||
>
|
||||
{props.options.map((opt) => (
|
||||
<Listbox.Option
|
||||
className={({ active }) =>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Trans, useTranslation } from "react-i18next";
|
|||
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { Toggle } from "@/components/buttons/Toggle";
|
||||
import { Dropdown } from "@/components/form/Dropdown";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { SettingsCard } from "@/components/layout/SettingsCard";
|
||||
import { Stepper } from "@/components/layout/Stepper";
|
||||
|
|
@ -36,6 +37,7 @@ import {
|
|||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||
import { conf } from "@/setup/config";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { Region, useRegionStore } from "@/utils/detectRegion";
|
||||
import { getProxyUrls } from "@/utils/proxyUrls";
|
||||
|
||||
import { Status, testFebboxToken } from "../parts/settings/SetupPart";
|
||||
|
|
@ -53,6 +55,7 @@ export function FEDAPISetup() {
|
|||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const febboxToken = useAuthStore((s) => s.febboxToken);
|
||||
const setFebboxToken = useAuthStore((s) => s.setFebboxToken);
|
||||
const { region, setRegion } = useRegionStore();
|
||||
|
||||
const [status, setStatus] = useState<Status>("unset");
|
||||
const statusMap: Record<Status, StatusCircleProps["type"]> = {
|
||||
|
|
@ -61,6 +64,14 @@ export function FEDAPISetup() {
|
|||
unset: "noresult",
|
||||
};
|
||||
|
||||
const regionOptions = [
|
||||
{ id: "us-east", name: "US East" },
|
||||
{ id: "us-west", name: "US West" },
|
||||
{ id: "south-america", name: "South America" },
|
||||
{ id: "asia", name: "Asia" },
|
||||
{ id: "europe", name: "Europe" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const checkTokenStatus = async () => {
|
||||
const result = await getFebboxTokenStatus(febboxToken);
|
||||
|
|
@ -165,6 +176,21 @@ export function FEDAPISetup() {
|
|||
passwordToggleable
|
||||
className="flex-grow"
|
||||
/>
|
||||
<div className="ml-4 w-40">
|
||||
<Dropdown
|
||||
options={regionOptions}
|
||||
selectedItem={{
|
||||
id: region || "us-east",
|
||||
name:
|
||||
regionOptions.find((r) => r.id === region)?.name ||
|
||||
"US East",
|
||||
}}
|
||||
setSelectedItem={(item) =>
|
||||
setRegion(item.id as Region, true)
|
||||
}
|
||||
direction="up"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{status === "error" && (
|
||||
<p className="text-type-danger mt-4">
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Trans, useTranslation } from "react-i18next";
|
|||
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { Toggle } from "@/components/buttons/Toggle";
|
||||
import { Dropdown } from "@/components/form/Dropdown";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { SettingsCard } from "@/components/layout/SettingsCard";
|
||||
import {
|
||||
|
|
@ -26,6 +27,7 @@ import {
|
|||
} from "@/pages/parts/settings/SetupPart";
|
||||
import { conf } from "@/setup/config";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { Region, useRegionStore } from "@/utils/detectRegion";
|
||||
|
||||
interface ProxyEditProps {
|
||||
proxyUrls: string[] | null;
|
||||
|
|
@ -229,6 +231,7 @@ async function getFebboxTokenStatus(febboxToken: string | null) {
|
|||
function FebboxTokenEdit({ febboxToken, setFebboxToken }: FebboxTokenProps) {
|
||||
const { t } = useTranslation();
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const { region, setRegion } = useRegionStore();
|
||||
|
||||
const [status, setStatus] = useState<Status>("unset");
|
||||
const statusMap: Record<Status, StatusCircleProps["type"]> = {
|
||||
|
|
@ -237,6 +240,14 @@ function FebboxTokenEdit({ febboxToken, setFebboxToken }: FebboxTokenProps) {
|
|||
unset: "noresult",
|
||||
};
|
||||
|
||||
const regionOptions = [
|
||||
{ id: "us-east", name: "US East" },
|
||||
{ id: "us-west", name: "US West" },
|
||||
{ id: "south-america", name: "South America" },
|
||||
{ id: "asia", name: "Asia" },
|
||||
{ id: "europe", name: "Europe" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const checkTokenStatus = async () => {
|
||||
const result = await getFebboxTokenStatus(febboxToken);
|
||||
|
|
@ -337,6 +348,19 @@ function FebboxTokenEdit({ febboxToken, setFebboxToken }: FebboxTokenProps) {
|
|||
passwordToggleable
|
||||
className="flex-grow"
|
||||
/>
|
||||
<div className="ml-4 w-40">
|
||||
<Dropdown
|
||||
options={regionOptions}
|
||||
selectedItem={{
|
||||
id: region || "us-east",
|
||||
name:
|
||||
regionOptions.find((r) => r.id === region)?.name ||
|
||||
"US East",
|
||||
}}
|
||||
setSelectedItem={(item) => setRegion(item.id as Region, true)}
|
||||
direction="up"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{status === "error" && (
|
||||
<p className="text-type-danger mt-4">
|
||||
|
|
|
|||
|
|
@ -12,18 +12,22 @@ export type Region =
|
|||
interface RegionStore {
|
||||
region: Region | null;
|
||||
lastChecked: number | null;
|
||||
setRegion: (region: Region) => void;
|
||||
userPicked: boolean;
|
||||
setRegion: (region: Region, userPicked?: boolean) => void;
|
||||
}
|
||||
|
||||
// const TEN_DAYS_MS = 10 * 24 * 60 * 60 * 1000;
|
||||
const THIRTY_MINUTES_MS = 30 * 60 * 1000;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
// const THIRTY_MINUTES_MS = 30 * 60 * 1000;
|
||||
|
||||
export const useRegionStore = create<RegionStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
region: null,
|
||||
lastChecked: null,
|
||||
setRegion: (region) => set({ region, lastChecked: Date.now() }),
|
||||
userPicked: false,
|
||||
setRegion: (region, userPicked = false) =>
|
||||
set({ region, lastChecked: Date.now(), userPicked }),
|
||||
}),
|
||||
{
|
||||
name: "__MW::region",
|
||||
|
|
@ -48,10 +52,16 @@ function determineRegion(data: {
|
|||
export async function detectRegion(): Promise<Region> {
|
||||
const store = useRegionStore.getState();
|
||||
|
||||
// If user picked a region, always return that
|
||||
if (store.userPicked && store.region) {
|
||||
return store.region;
|
||||
}
|
||||
|
||||
// If we have a recent detection, return that
|
||||
if (
|
||||
store.region &&
|
||||
store.lastChecked &&
|
||||
Date.now() - store.lastChecked < THIRTY_MINUTES_MS
|
||||
Date.now() - store.lastChecked < ONE_DAY_MS
|
||||
) {
|
||||
return store.region;
|
||||
}
|
||||
|
|
@ -61,7 +71,9 @@ export async function detectRegion(): Promise<Region> {
|
|||
const data = await response.json();
|
||||
|
||||
const detectedRegion = determineRegion(data);
|
||||
store.setRegion(detectedRegion); // Persist the detected region
|
||||
if (!store.userPicked) {
|
||||
store.setRegion(detectedRegion); // Only update if not user picked
|
||||
}
|
||||
return detectedRegion;
|
||||
} catch (error) {
|
||||
console.warn("Failed to detect region:", error);
|
||||
|
|
|
|||
Loading…
Reference in a new issue