feat: implement user account management and dashboard components.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import type { User } from "@supabase/supabase-js";
|
import type { User } from "@supabase/supabase-js";
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +41,17 @@ import {
|
|||||||
getSupabaseBrowserClient,
|
getSupabaseBrowserClient,
|
||||||
hasSupabasePublicEnv,
|
hasSupabasePublicEnv,
|
||||||
} from "@/lib/supabase/client";
|
} from "@/lib/supabase/client";
|
||||||
import { UnlockProOverlay } from "@/components/subscription/UnlockProOverlay";
|
|
||||||
|
const UnlockProOverlay = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/subscription/UnlockProOverlay").then(
|
||||||
|
(module) => module.UnlockProOverlay,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// --- Types ---
|
// --- Types ---
|
||||||
|
|
||||||
@@ -612,16 +623,20 @@ export function AccountCenter() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const authHeaders = await buildAuthedHeaders(false);
|
const authHeadersPromise = buildAuthedHeaders(false);
|
||||||
const [configRes, walletsRes] = await Promise.all([
|
const [configRes, walletsRes] = await Promise.all([
|
||||||
fetch("/api/payments/config", {
|
authHeadersPromise.then((headers) =>
|
||||||
cache: "no-store",
|
fetch("/api/payments/config", {
|
||||||
headers: authHeaders,
|
cache: "no-store",
|
||||||
}),
|
headers,
|
||||||
fetch("/api/payments/wallets", {
|
}),
|
||||||
cache: "no-store",
|
),
|
||||||
headers: authHeaders,
|
authHeadersPromise.then((headers) =>
|
||||||
}),
|
fetch("/api/payments/wallets", {
|
||||||
|
cache: "no-store",
|
||||||
|
headers,
|
||||||
|
}),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
if (configRes.ok) {
|
if (configRes.ok) {
|
||||||
const configJson = (await configRes.json()) as PaymentConfig;
|
const configJson = (await configRes.json()) as PaymentConfig;
|
||||||
@@ -674,11 +689,13 @@ export function AccountCenter() {
|
|||||||
const userPromise = supabaseReady
|
const userPromise = supabaseReady
|
||||||
? getSupabaseBrowserClient().auth.getUser()
|
? getSupabaseBrowserClient().auth.getUser()
|
||||||
: Promise.resolve({ data: { user: null as User | null } });
|
: Promise.resolve({ data: { user: null as User | null } });
|
||||||
const authHeaders = await buildAuthedHeaders(false);
|
const authHeadersPromise = buildAuthedHeaders(false);
|
||||||
const backendPromise = fetch("/api/auth/me", {
|
const backendPromise = authHeadersPromise.then((headers) =>
|
||||||
cache: "no-store",
|
fetch("/api/auth/me", {
|
||||||
headers: authHeaders,
|
cache: "no-store",
|
||||||
});
|
headers,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const [userResult, backendResult] = await Promise.all([
|
const [userResult, backendResult] = await Promise.all([
|
||||||
userPromise,
|
userPromise,
|
||||||
backendPromise,
|
backendPromise,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
import styles from "./Dashboard.module.css";
|
import styles from "./Dashboard.module.css";
|
||||||
import {
|
import {
|
||||||
DashboardStoreProvider,
|
DashboardStoreProvider,
|
||||||
@@ -9,11 +10,47 @@ import {
|
|||||||
import { I18nProvider, useI18n } from "@/hooks/useI18n";
|
import { I18nProvider, useI18n } from "@/hooks/useI18n";
|
||||||
import { CitySidebar } from "@/components/dashboard/CitySidebar";
|
import { CitySidebar } from "@/components/dashboard/CitySidebar";
|
||||||
import { DetailPanel } from "@/components/dashboard/DetailPanel";
|
import { DetailPanel } from "@/components/dashboard/DetailPanel";
|
||||||
import { FutureForecastModal } from "@/components/dashboard/FutureForecastModal";
|
|
||||||
import { GuideModal } from "@/components/dashboard/GuideModal";
|
|
||||||
import { HeaderBar } from "@/components/dashboard/HeaderBar";
|
import { HeaderBar } from "@/components/dashboard/HeaderBar";
|
||||||
import { HistoryModal } from "@/components/dashboard/HistoryModal";
|
|
||||||
import { MapCanvas } from "@/components/dashboard/MapCanvas";
|
const MapCanvas = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/dashboard/MapCanvas").then((module) => module.MapCanvas),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <div className="map" aria-hidden="true" />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const GuideModal = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/dashboard/GuideModal").then((module) => module.GuideModal),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const HistoryModal = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/dashboard/HistoryModal").then(
|
||||||
|
(module) => module.HistoryModal,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const FutureForecastModal = dynamic(
|
||||||
|
() =>
|
||||||
|
import("@/components/dashboard/FutureForecastModal").then(
|
||||||
|
(module) => module.FutureForecastModal,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function DashboardScreen() {
|
function DashboardScreen() {
|
||||||
const store = useDashboardStore();
|
const store = useDashboardStore();
|
||||||
@@ -57,9 +94,9 @@ function DashboardScreen() {
|
|||||||
<HeaderBar />
|
<HeaderBar />
|
||||||
<CitySidebar />
|
<CitySidebar />
|
||||||
<DetailPanel />
|
<DetailPanel />
|
||||||
<GuideModal />
|
{store.isGuideOpen && <GuideModal />}
|
||||||
<HistoryModal />
|
{store.historyState.isOpen && <HistoryModal />}
|
||||||
<FutureForecastModal />
|
{store.futureModalDate && <FutureForecastModal />}
|
||||||
{showLoading && (
|
{showLoading && (
|
||||||
<div className="loading-overlay">
|
<div className="loading-overlay">
|
||||||
<div className="loading-spinner" />
|
<div className="loading-spinner" />
|
||||||
|
|||||||
Reference in New Issue
Block a user