Reverted to edit edt-ac485f97-7774-4a29-9d0a-ec4295ecbab8: "Configure Supabase environment variables
Sets Supabase URL and anon key using environment variables."
This commit is contained in:
3
.env
3
.env
@@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
VITE_SUPABASE_URL=your_supabase_url_here
|
|
||||||
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
VITE_SUPABASE_URL=your_supabase_url_here
|
|
||||||
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
|
||||||
43
package-lock.json
generated
43
package-lock.json
generated
@@ -42,7 +42,6 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@supabase/supabase-js": "^2.49.1",
|
"@supabase/supabase-js": "^2.49.1",
|
||||||
"@tanstack/react-query": "^5.56.2",
|
"@tanstack/react-query": "^5.56.2",
|
||||||
"@types/react-helmet": "^6.1.11",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
@@ -54,7 +53,6 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-helmet": "^6.1.0",
|
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-resizable-panels": "^2.1.3",
|
"react-resizable-panels": "^2.1.3",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
@@ -3295,12 +3293,14 @@
|
|||||||
"version": "15.7.13",
|
"version": "15.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.12",
|
"version": "18.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
||||||
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -3317,15 +3317,6 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-helmet": {
|
|
||||||
"version": "6.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
|
|
||||||
"integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/slice-ansi": {
|
"node_modules/@types/slice-ansi": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
||||||
@@ -6845,27 +6836,6 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-fast-compare": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
|
||||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/react-helmet": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
|
||||||
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"react-fast-compare": "^3.1.1",
|
|
||||||
"react-side-effect": "^2.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.53.1",
|
"version": "7.53.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz",
|
||||||
@@ -6977,15 +6947,6 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-side-effect": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-smooth": {
|
"node_modules/react-smooth": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@supabase/supabase-js": "^2.49.1",
|
"@supabase/supabase-js": "^2.49.1",
|
||||||
"@tanstack/react-query": "^5.56.2",
|
"@tanstack/react-query": "^5.56.2",
|
||||||
"@types/react-helmet": "^6.1.11",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
@@ -57,7 +56,6 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-helmet": "^6.1.0",
|
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-resizable-panels": "^2.1.3",
|
"react-resizable-panels": "^2.1.3",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
|
|||||||
@@ -2,16 +2,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Bell } from 'lucide-react';
|
import { Bell } from 'lucide-react';
|
||||||
|
|
||||||
interface HeaderProps {
|
const Header: React.FC = () => {
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Header: React.FC<HeaderProps> = ({ title }) => {
|
|
||||||
return (
|
return (
|
||||||
<header className="py-8">
|
<header className="py-8">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold neuro-text">{title || '반갑습니다'}</h1>
|
<h1 className="text-2xl font-bold neuro-text">반갑습니다</h1>
|
||||||
<p className="text-gray-500">젤리의 적자탈출</p>
|
<p className="text-gray-500">젤리의 적자탈출</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="neuro-flat p-2.5 rounded-full">
|
<button className="neuro-flat p-2.5 rounded-full">
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { CircleCheck, CircleAlert, RefreshCw, Database, AlertCircle } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
testSupabaseConnection,
|
|
||||||
checkSupabaseEnvironment,
|
|
||||||
checkSupabaseTables
|
|
||||||
} from '@/utils/supabaseTest';
|
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
||||||
|
|
||||||
const SupabaseTestPanel = () => {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [connectionStatus, setConnectionStatus] = useState<{
|
|
||||||
tested: boolean;
|
|
||||||
success?: boolean;
|
|
||||||
message?: string;
|
|
||||||
}>({ tested: false });
|
|
||||||
|
|
||||||
const [envStatus] = useState(() => checkSupabaseEnvironment());
|
|
||||||
const [tablesStatus, setTablesStatus] = useState<{
|
|
||||||
checked: boolean;
|
|
||||||
exists?: boolean;
|
|
||||||
tables?: string[];
|
|
||||||
}>({ checked: false });
|
|
||||||
|
|
||||||
const runConnectionTest = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const result = await testSupabaseConnection();
|
|
||||||
setConnectionStatus({
|
|
||||||
tested: true,
|
|
||||||
success: result.success,
|
|
||||||
message: result.message
|
|
||||||
});
|
|
||||||
|
|
||||||
// 연결 성공 시 테이블도 확인
|
|
||||||
if (result.success) {
|
|
||||||
const tablesResult = await checkSupabaseTables();
|
|
||||||
setTablesStatus({
|
|
||||||
checked: true,
|
|
||||||
exists: tablesResult.exists,
|
|
||||||
tables: tablesResult.tables
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setConnectionStatus({
|
|
||||||
tested: true,
|
|
||||||
success: false,
|
|
||||||
message: error instanceof Error ? error.message : '알 수 없는 오류'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="w-full max-w-md mx-auto">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Database className="h-5 w-5" />
|
|
||||||
Supabase 연결 테스트
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
백엔드 서비스 연결 상태를 확인합니다
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
{/* 환경 변수 설정이 잘못된 경우 경고 표시 */}
|
|
||||||
{!envStatus.valid && (
|
|
||||||
<Alert variant="destructive" className="mb-4">
|
|
||||||
<AlertCircle className="h-4 w-4" />
|
|
||||||
<AlertDescription>
|
|
||||||
<p className="font-medium">환경 변수 설정이 필요합니다</p>
|
|
||||||
<p className="text-xs mt-1">.env 파일을 업데이트하여 Supabase URL과 API 키를 설정해주세요.</p>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 환경 변수 상태 */}
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">환경 변수 설정</span>
|
|
||||||
<Badge
|
|
||||||
variant={envStatus.valid ? "outline" : "destructive"}
|
|
||||||
className={envStatus.valid ? "bg-green-50 text-green-700" : ""}
|
|
||||||
>
|
|
||||||
{envStatus.valid ? (
|
|
||||||
<CircleCheck className="h-3 w-3 mr-1" />
|
|
||||||
) : (
|
|
||||||
<CircleAlert className="h-3 w-3 mr-1" />
|
|
||||||
)}
|
|
||||||
{envStatus.valid ? '설정됨' : '오류'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!envStatus.valid && (
|
|
||||||
<p className="text-xs text-red-500">{envStatus.message}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 연결 테스트 */}
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">서버 연결 상태</span>
|
|
||||||
{connectionStatus.tested ? (
|
|
||||||
<Badge
|
|
||||||
variant={connectionStatus.success ? "outline" : "destructive"}
|
|
||||||
className={connectionStatus.success ? "bg-green-50 text-green-700" : ""}
|
|
||||||
>
|
|
||||||
{connectionStatus.success ? (
|
|
||||||
<CircleCheck className="h-3 w-3 mr-1" />
|
|
||||||
) : (
|
|
||||||
<CircleAlert className="h-3 w-3 mr-1" />
|
|
||||||
)}
|
|
||||||
{connectionStatus.success ? '연결됨' : '연결 실패'}
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge variant="secondary">테스트 필요</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{connectionStatus.tested && (
|
|
||||||
<p className="text-xs text-gray-500">{connectionStatus.message}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 테이블 확인 */}
|
|
||||||
{tablesStatus.checked && (
|
|
||||||
<>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">테이블 구조</span>
|
|
||||||
<Badge
|
|
||||||
variant={tablesStatus.exists ? "outline" : "secondary"}
|
|
||||||
className={tablesStatus.exists ? "bg-green-50 text-green-700" : ""}
|
|
||||||
>
|
|
||||||
{tablesStatus.exists ? '완료' : '미설정'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{tablesStatus.tables && tablesStatus.tables.length > 0 && (
|
|
||||||
<div className="text-xs text-gray-500">
|
|
||||||
<p>확인된 테이블:</p>
|
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
|
||||||
{tablesStatus.tables.map(table => (
|
|
||||||
<Badge key={table} variant="secondary" className="text-xs">
|
|
||||||
{table}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={runConnectionTest}
|
|
||||||
disabled={loading || !envStatus.valid}
|
|
||||||
className="w-full mt-4"
|
|
||||||
>
|
|
||||||
{loading && <RefreshCw className="mr-2 h-4 w-4 animate-spin" />}
|
|
||||||
{loading ? '테스트 중...' : '연결 테스트 실행'}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{!envStatus.valid && (
|
|
||||||
<p className="text-xs text-center text-muted-foreground mt-2">
|
|
||||||
테스트를 실행하기 전에 먼저 .env 파일을 업데이트해주세요.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SupabaseTestPanel;
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
// Supabase URL과 anon key 설정
|
// Supabase 온프레미스 URL과 anon key 설정
|
||||||
// 환경변수에서 가져오기
|
// 환경 변수를 우선적으로 사용하되, 하드코딩된 값을 기본값으로 설정
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://xguihxuzqibwxjnimxev.supabase.co';
|
||||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhndWloeHV6cWlid3hqbmlteGV2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NzUwOTQ4MzUsImV4cCI6MTk5MDY3MDgzNX0.0PMlOxtKL4O9GGZuAP_Xl4f-Tut1qOnW4bNEmAtoB8w';
|
||||||
|
|
||||||
// 유효한 URL이 설정되었는지 확인
|
// 유효한 URL이 설정되었는지 확인
|
||||||
const isValidUrl = supabaseUrl && supabaseAnonKey &&
|
const isValidUrl = supabaseUrl && supabaseAnonKey &&
|
||||||
|
|||||||
@@ -1,172 +1,89 @@
|
|||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import NavBar from "@/components/NavBar";
|
import NavBar from '@/components/NavBar';
|
||||||
import Header from "@/components/Header";
|
import SyncSettings from '@/components/SyncSettings';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { User, CreditCard, Bell, Lock, HelpCircle, LogOut, ChevronRight } from 'lucide-react';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { cn } from '@/lib/utils';
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Bell, Lock, Moon, User, HelpCircle, CreditCard } from "lucide-react";
|
const SettingsOption = ({
|
||||||
import SyncSettings from "@/components/SyncSettings";
|
icon: Icon,
|
||||||
import SupabaseTestPanel from "@/components/SupabaseTestPanel";
|
label,
|
||||||
|
description,
|
||||||
|
onClick,
|
||||||
|
color = "text-neuro-income"
|
||||||
|
}: {
|
||||||
|
icon: React.ElementType;
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
color?: string;
|
||||||
|
}) => {
|
||||||
|
return <div className="neuro-flat p-4 transition-all duration-300 hover:shadow-neuro-convex cursor-pointer" onClick={onClick}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className={cn("neuro-pressed p-3 rounded-full mr-4", color)}>
|
||||||
|
<Icon size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium">{label}</h3>
|
||||||
|
{description && <p className="text-xs text-gray-500">{description}</p>}
|
||||||
|
</div>
|
||||||
|
<ChevronRight size={18} className="text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
return <div className="min-h-screen bg-neuro-background pb-24">
|
||||||
|
<div className="max-w-md mx-auto px-6">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="py-8">
|
||||||
|
<h1 className="text-2xl font-bold neuro-text mb-6">설정</h1>
|
||||||
|
|
||||||
return (
|
{/* User Profile */}
|
||||||
<div className="min-h-screen bg-background">
|
<div className="neuro-flat p-6 mb-8 flex items-center">
|
||||||
<Helmet>
|
<div className="neuro-flat p-3 rounded-full mr-4 text-neuro-income">
|
||||||
<title>설정 | 예산 관리</title>
|
<User size={24} />
|
||||||
</Helmet>
|
|
||||||
<Header title="설정" />
|
|
||||||
|
|
||||||
<main className="container mx-auto pt-6 pb-32">
|
|
||||||
<Tabs defaultValue="general" className="w-full">
|
|
||||||
<TabsList className="grid grid-cols-2 w-full max-w-md mx-auto mb-8">
|
|
||||||
<TabsTrigger value="general">일반 설정</TabsTrigger>
|
|
||||||
<TabsTrigger value="advanced">고급 설정</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="general" className="space-y-6">
|
|
||||||
{/* 프로필 관리 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>사용자 설정</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
계정 정보 및 개인 설정을 관리합니다.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-between"
|
|
||||||
onClick={() => navigate('/profile')}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<User className="h-4 w-4" />
|
|
||||||
<span>프로필 관리</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span>›</span>
|
<div>
|
||||||
</Button>
|
<h2 className="font-semibold text-lg">홍길동</h2>
|
||||||
|
<p className="text-sm text-gray-500">honggildong@example.com</p>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-between"
|
|
||||||
onClick={() => navigate('/security-privacy')}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Lock className="h-4 w-4" />
|
|
||||||
<span>보안 및 개인정보</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span>›</span>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-between"
|
|
||||||
onClick={() => navigate('/notifications')}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Bell className="h-4 w-4" />
|
|
||||||
<span>알림 설정</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span>›</span>
|
</header>
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 결제 방법 */}
|
{/* Data Sync Settings */}
|
||||||
<Card>
|
<div className="mb-8">
|
||||||
<CardHeader>
|
<h2 className="text-sm font-medium text-gray-500 mb-2 px-2">데이터 동기화</h2>
|
||||||
<CardTitle>결제 방법</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
결제 카드 및 계좌 정보를 관리합니다.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-between"
|
|
||||||
onClick={() => navigate('/payment-methods')}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<CreditCard className="h-4 w-4" />
|
|
||||||
<span>결제 수단 관리</span>
|
|
||||||
</div>
|
|
||||||
<span>›</span>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 동기화 설정 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>동기화 설정</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
클라우드 동기화 및 백업 기능을 관리합니다.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<SyncSettings />
|
<SyncSettings />
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Supabase 테스트 패널 */}
|
|
||||||
<SupabaseTestPanel />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="advanced" className="space-y-6">
|
|
||||||
{/* 테마 설정 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>앱 설정</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
앱의 기본 설정 및 테마를 관리합니다.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-between"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Moon className="h-4 w-4" />
|
|
||||||
<span>테마 설정</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span>준비 중</span>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 도움말 및 지원 */}
|
{/* Settings Options */}
|
||||||
<Card>
|
<div className="space-y-4 mb-8">
|
||||||
<CardHeader>
|
<h2 className="text-sm font-medium text-gray-500 mb-2 px-2">계정</h2>
|
||||||
<CardTitle>도움말 및 지원</CardTitle>
|
<SettingsOption icon={User} label="프로필 관리" description="프로필 및 비밀번호 설정" onClick={() => navigate('/profile-management')} />
|
||||||
<CardDescription>
|
<SettingsOption icon={CreditCard} label="결제 방법" description="카드 및 은행 계좌 관리" onClick={() => navigate('/payment-methods')} />
|
||||||
문의사항 및 문제 해결을 위한 지원을 받으세요.
|
<SettingsOption icon={Bell} label="알림 설정" description="앱 알림 및 리마인더" onClick={() => navigate('/notification-settings')} />
|
||||||
</CardDescription>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
<div className="space-y-4 mb-8">
|
||||||
<Button
|
<h2 className="text-sm font-medium text-gray-500 mb-2 px-2">앱 설정</h2>
|
||||||
variant="outline"
|
<SettingsOption icon={Lock} label="보안 및 개인정보" description="보안 및 데이터 설정" onClick={() => navigate('/security-privacy-settings')} />
|
||||||
className="w-full justify-between"
|
<SettingsOption icon={HelpCircle} label="도움말 및 지원" description="FAQ 및 고객 지원" onClick={() => navigate('/help-support')} />
|
||||||
onClick={() => navigate('/help')}
|
</div>
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="mt-8">
|
||||||
<HelpCircle className="h-4 w-4" />
|
<SettingsOption icon={LogOut} label="로그아웃" color="text-neuro-expense" />
|
||||||
<span>도움말 보기</span>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 text-center text-xs text-gray-400">
|
||||||
|
<p>앱 버전 0.1</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span>›</span>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
|
|
||||||
import { supabase } from '@/lib/supabase';
|
|
||||||
import { toast } from '@/components/ui/use-toast';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supabase 환경 설정 확인
|
|
||||||
* 환경 변수가 올바르게 설정되었는지 확인합니다.
|
|
||||||
*/
|
|
||||||
export const checkSupabaseEnvironment = (): { valid: boolean; message: string } => {
|
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
||||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
||||||
|
|
||||||
if (!supabaseUrl || !supabaseAnonKey) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: '환경 변수가 설정되지 않았습니다.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supabaseUrl.includes('your_supabase_url_here') ||
|
|
||||||
supabaseAnonKey.includes('your_supabase_anon_key_here')) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: '환경 변수가 아직 실제 값으로 설정되지 않았습니다. .env 파일을 업데이트해주세요.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
message: '환경 변수가 올바르게 설정되었습니다.'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supabase 연결 테스트
|
|
||||||
* 현재 Supabase 연결 상태를 확인합니다.
|
|
||||||
*/
|
|
||||||
export const testSupabaseConnection = async (): Promise<{ success: boolean; message: string }> => {
|
|
||||||
// 먼저 환경 설정 확인
|
|
||||||
const envCheck = checkSupabaseEnvironment();
|
|
||||||
if (!envCheck.valid) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `환경 설정 오류: ${envCheck.message}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 서버에 핑 보내기
|
|
||||||
const { data, error } = await supabase.from('_ping').select('*').limit(1);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('Supabase 연결 테스트 실패:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `연결 오류: ${error.message}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 사용자 세션 확인
|
|
||||||
const { data: userData, error: userError } = await supabase.auth.getUser();
|
|
||||||
const isLoggedIn = userData?.user !== null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: `Supabase 연결 성공! 로그인 상태: ${isLoggedIn ? '로그인됨' : '로그인 필요'}`
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
console.error('Supabase 테스트 중 예외 발생:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `예외 발생: ${error.message}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용자 인터페이스에서 Supabase 연결 테스트를 실행
|
|
||||||
* 결과를 토스트 메시지로 표시합니다.
|
|
||||||
*/
|
|
||||||
export const runSupabaseConnectionTest = async () => {
|
|
||||||
toast({
|
|
||||||
title: "Supabase 연결 테스트",
|
|
||||||
description: "연결 상태를 확인 중입니다...",
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await testSupabaseConnection();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
toast({
|
|
||||||
title: "연결 성공",
|
|
||||||
description: result.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: "연결 실패",
|
|
||||||
description: result.message,
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supabase 테이블 구조 확인
|
|
||||||
* 지정된 테이블이 존재하는지 확인합니다.
|
|
||||||
*/
|
|
||||||
export const checkSupabaseTables = async (): Promise<{ exists: boolean; tables: string[] }> => {
|
|
||||||
// 먼저 환경 설정 확인
|
|
||||||
const envCheck = checkSupabaseEnvironment();
|
|
||||||
if (!envCheck.valid) {
|
|
||||||
return { exists: false, tables: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 현재 스키마의 테이블 목록 가져오기
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from('pg_tables')
|
|
||||||
.select('tablename')
|
|
||||||
.eq('schemaname', 'public');
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('테이블 확인 오류:', error);
|
|
||||||
return { exists: false, tables: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const tables = data?.map(table => table.tablename) || [];
|
|
||||||
|
|
||||||
// 필요한 테이블이 있는지 확인
|
|
||||||
const requiredTables = ['transactions', 'budgets', 'category_budgets'];
|
|
||||||
const missingTables = requiredTables.filter(table => !tables.includes(table));
|
|
||||||
|
|
||||||
return {
|
|
||||||
exists: missingTables.length === 0,
|
|
||||||
tables
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('테이블 확인 중 오류 발생:', error);
|
|
||||||
return { exists: false, tables: [] };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user