Rumah >hujung hadapan web >tutorial js >Membina Penukar Mata Wang Automatik dalam Next.js

Membina Penukar Mata Wang Automatik dalam Next.js

Mary-Kate Olsen
Mary-Kate Olsenasal
2025-01-03 08:33:43436semak imbas

Prasyarat
Sebelum anda bermula, pastikan anda mempunyai pemahaman asas tentang Next.js dan React.

1. Mencipta Laluan API Bahagian Belakang

Kami akan mencipta laluan API Next.js yang berinteraksi dengan API Geolokasi kami.
Cipta fail baharu di: 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. Mendapatkan Kunci API Anda

Kami akan menggunakan perkhidmatan geolokasi percuma yang dipanggil IP Flare. Lawati Halaman Kunci API: Navigasi ke halaman Kunci API.

Lawati: www.ipflare.io

Dari halaman Kunci API kami boleh mendapatkan kunci API kami dan kami boleh menggunakan salinan pantas untuk menyimpannya sebagai pembolehubah persekitaran dalam fail .env kami. Kami akan menggunakan ini untuk mengesahkan permintaan kami.
Building an Automatic Currency Switcher in Next.js

3. Mencipta Komponen Bahagian Hadapan

Saya telah mencipta komponen semua-dalam-satu ini yang merangkumi pembekal dan pemilih mata wang. Saya menggunakan shadcn/ui dan beberapa SVG bendera yang saya temui dalam talian.

Anda perlu membungkus aplikasi dalam supaya kita boleh mengakses konteks.

Sekarang, di mana-mana dalam aplikasi di mana kita ingin mengakses mata wang, kita boleh menggunakan cangkuk const { currency } = useCurrency();.

Untuk menyepadukan ini dengan Stripe, apabila anda membuat pembayaran, anda hanya perlu menghantar mata wang dan memastikan bahawa anda telah menambahkan harga berbilang mata wang pada produk Stripe anda.

"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;
};

Atas ialah kandungan terperinci Membina Penukar Mata Wang Automatik dalam Next.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn