登录
首页 >  文章 >  前端

在 Nextjs 中构建自动货币切换器

来源:dev.to

时间:2025-01-16 13:55:03 113浏览 收藏

欢迎各位小伙伴来到golang学习网,相聚于此都是缘哈哈哈!今天我给大家带来《在 Nextjs 中构建自动货币切换器》,这篇文章主要讲到等等知识,如果你对文章相关的知识非常感兴趣或者正在自学,都可以关注我,我会持续更新相关文章!当然,有什么建议也欢迎在评论留言提出!一起学习!

先决条件
在开始之前,请确保您对 next.js 和 react 有基本的了解。

1. 创建后端api路由

我们将创建一个与我们的 geolocation api 交互的 next.js api 路由。
在以下位置创建一个新文件:src/app/api/geolocation/route.ts

import { nextresponse } from "next/server";
import axios from "axios";

type ipgeolocation = {
    ip: string;
    version?: string;
    city?: string;
    region?: string;
    region_code?: string;
    country_code?: string;
    country_code_iso3?: string;
    country_fifa_code?: string;
    country_fips_code?: string;
    country_name?: string;
    country_capital?: string;
    country_tld?: string;
    country_emoji?: string;
    continent_code?: string;
    in_eu: boolean;
    land_locked: boolean;
    postal?: string;
    latitude?: number;
    longitude?: number;
    timezone?: string;
    utc_offset?: string;
    country_calling_code?: string;
    currency?: string;
    currency_name?: string;
    languages?: string;
    country_area?: number;
    asn?: string; // append ?fields=asn to the url
    isp?: string; // append ?fields=isp to the url
}

type ipgeolocationerror = {
  code: string;
  error: string;
}

export async function get() {
  // retrieve ip address using the getclientip function
  // for testing purposes, we'll use a fixed ip address
  // const clientip = getclientip(req.headers);

  const clientip = "84.17.50.173";

  if (!clientip) {
    return nextresponse.json(
      { error: "unable to determine ip address" },
      { status: 400 }
    );
  }

  const key = process.env.ipflare_api_key;

  if (!key) {
    return nextresponse.json(
      { error: "ipflare api key is not set" },
      { status: 500 }
    );
  }

  try {
    const response = await axios.get<ipgeolocation | ipgeolocationerror>(
      `https://api.ipflare.io/${clientip}`,
      {
        headers: {
          "x-api-key": key,
        },
      }
    );

    if ("error" in response.data) {
      return nextresponse.json({ error: response.data.error }, { status: 400 });
    }

    return nextresponse.json(response.data);
  } catch {
    return nextresponse.json(
      { error: "internal server error" },
      { status: 500 }
    );
  }
}

2. 获取您的api密钥

我们将使用名为 ip flare 的免费地理定位服务。访问 api 密钥页面:导航至 api 密钥页面。

访问:www.ipflare.io

从 api 密钥页面我们可以获得 api 密钥,并且可以使用快速复制将其作为环境变量存储在 .env 文件中。我们将用它来验证我们的请求。
image description

3. 创建前端组件

我创建了这个一体化组件,其中包括提供程序和货币选择器。我正在使用 shadcn/ui 和一些我在网上找到的标志 svg。

您需要将应用程序包装在 <currencyprovider /> 中,以便我们可以访问上下文。

现在,在应用程序中任何想要访问货币的地方,我们都可以使用钩子 const {currency } = usecurrency();。

要将其与 stripe 集成,当您创建结帐时,您只需发送货币并确保已将多货币定价添加到您的 stripe 产品中。

"use client";

import { useRouter } from "next/navigation";
import {
  createContext,
  type FC,
  type ReactNode,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import axios from "axios"; // 1) Import axios
import { Flag } from "~/components/flag";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "~/components/ui/select";
import { cn } from "~/lib/utils";
import { type Currency } from "~/server/schemas/currency";

// -- [1] Create a local type for the data returned by /api/geolocation.
type GeolocationData = {
  country_code?: string;
  continent_code?: string;
  currency?: string;
};

type CurrencyContext = {
  currency: Currency;
  setCurrency: (currency: Currency) => void;
};

const CurrencyContext = createContext<CurrencyContext | null>(null);

export function useCurrency() {
  const context = useContext(CurrencyContext);
  if (!context) {
    throw new Error("useCurrency must be used within a CurrencyProvider.");
  }
  return context;
}

export const CurrencyProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const router = useRouter();

  // -- [2] Local state for geolocation data
  const [location, setLocation] = useState<GeolocationData | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);

  // -- [3] Fetch location once when the component mounts
  useEffect(() => {
    const fetchLocation = async () => {
      setIsLoading(true);
      try {
        const response = await axios.get("/api/geolocation");
        setLocation(response.data);
      } catch (error) {
        console.error(error);
      } finally {
        setIsLoading(false);
      }
    };

    void fetchLocation();
  }, []);

  // -- [4] Extract currency from location if present (fallback to "usd")
  const geoCurrency = location?.currency;

  const getInitialCurrency = (): Currency => {
    if (typeof window !== "undefined") {
      const cookie = document.cookie
        .split("; ")
        .find((row) => row.startsWith("currency="));
      if (cookie) {
        const value = cookie.split("=")[1];
        if (value === "usd" || value === "eur" || value === "gbp") {
          return value;
        }
      }
    }
    return "usd";
  };

  const [currency, setCurrencyState] = useState<Currency>(getInitialCurrency);

  useEffect(() => {
    if (!isLoading && geoCurrency !== undefined) {
      const validatedCurrency = validateCurrency(geoCurrency, location);
      if (validatedCurrency) {
        setCurrency(validatedCurrency);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isLoading, location, geoCurrency]);

  // -- [5] Update currency & store cookie; no more tRPC invalidation
  const setCurrency = (newCurrency: Currency) => {
    setCurrencyState(newCurrency);
    if (typeof window !== "undefined") {
      document.cookie = `currency=${newCurrency}; path=/; max-age=${
        60 * 60 * 24 * 365
      }`; // Expires in 1 year
    }
    // Removed tRPC invalidate since we are no longer using tRPC
    router.refresh(); 
  };

  const contextValue = useMemo<CurrencyContext>(
    () => ({
      currency,
      setCurrency,
    }),
    [currency],
  );

  return (
    <CurrencyContext.Provider value={contextValue}>
      {children}
    </CurrencyContext.Provider>
  );
};

export const CurrencySelect = ({ className }: { className?: string }) => {
  const { currency, setCurrency } = useCurrency();
  return (
    <Select value={currency} onValueChange={setCurrency}>
      <SelectTrigger className={cn("w-[250px]", className)}>
        <SelectValue placeholder="Select a currency" />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup className="text-sm">
          <SelectItem value="usd">
            <div className="flex items-center gap-3">
              <Flag code="US" className="h-4 w-4 rounded" /> <span>$ USD</span>
            </div>
          </SelectItem>
          <SelectItem value="eur">
            <div className="flex items-center gap-3">
              <Flag code="EU" className="h-4 w-4 rounded" /> <span>€ EUR</span>
            </div>
          </SelectItem>
          <SelectItem value="gbp">
            <div className="flex items-center gap-3">
              <Flag code="GB" className="h-4 w-4 rounded" /> <span>£ GBP</span>
            </div>
          </SelectItem>
        </SelectGroup>
      </SelectContent>
    </Select>
  );
};

// -- [6] Use our new GeolocationData type in place of RouterOutputs
const validateCurrency = (
  currency: string,
  location?: GeolocationData | null,
): Currency | null => {
  if (currency === "usd" || currency === "eur" || currency === "gbp") {
    return currency;
  }

  if (!location) {
    return null;
  }

  if (location.country_code === "GB") {
    return "gbp";
  }

  // Check if they are in the EU
  if (location.continent_code === "EU") {
    return "eur";
  }

  // North America
  if (location.continent_code === "NA") {
    return "usd";
  }

  return null;
};

以上就是《在 Nextjs 中构建自动货币切换器》的详细内容,更多关于的资料请关注golang学习网公众号!

声明:本文转载于:dev.to 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>