feat: 暂时隐藏用户登录认证功能

This commit is contained in:
hzm
2026-02-06 17:38:50 +08:00
parent 8d9abdc1ff
commit bc0eca66f4
4 changed files with 759 additions and 34 deletions

View File

@@ -14,7 +14,8 @@
box-sizing: border-box;
}
html, body {
html,
body {
height: 100%;
}
@@ -65,13 +66,29 @@ body {
gap: 16px;
}
.col-12 { grid-column: span 12; }
.col-6 { grid-column: span 6; }
.col-4 { grid-column: span 4; }
.col-3 { grid-column: span 3; }
.col-12 {
grid-column: span 12;
}
.col-6 {
grid-column: span 6;
}
.col-4 {
grid-column: span 4;
}
.col-3 {
grid-column: span 3;
}
@media (max-width: 1024px) {
.col-6, .col-4, .col-3 { grid-column: span 12; }
.col-6,
.col-4,
.col-3 {
grid-column: span 12;
}
}
.navbar {
@@ -101,6 +118,7 @@ body {
.content {
padding-top: 90px;
}
.navbar {
top: 0;
left: 0;
@@ -110,6 +128,7 @@ body {
border-left: none;
border-right: none;
}
.add-fund-section {
margin-top: 60px;
}
@@ -133,6 +152,7 @@ body {
font-size: 14px;
transition: border-color 200ms ease, box-shadow 200ms ease;
}
.input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2);
@@ -144,6 +164,7 @@ input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
@@ -159,10 +180,12 @@ input[type="number"] {
cursor: pointer;
transition: transform 150ms ease, box-shadow 200ms ease;
}
.button:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(34, 211, 238, 0.25);
}
.button:active {
transform: translateY(0);
}
@@ -200,10 +223,12 @@ input[type="number"] {
background: #0b1220;
font-size: 12px;
}
.badge-v span {
font-size: 10px;
color: var(--muted);
}
.badge-v strong {
font-size: 11px;
}
@@ -213,14 +238,17 @@ input[type="number"] {
align-items: baseline;
gap: 8px;
}
.stat .label {
font-size: 12px;
color: var(--muted);
}
.stat .value {
font-size: 20px;
font-weight: 700;
}
.stat .badge {
padding: 4px 8px;
font-size: 12px;
@@ -228,60 +256,80 @@ input[type="number"] {
@media (max-width: 640px) {
.input {
font-size: 16px; /* 防止 iOS 在输入时自动放大 */
font-size: 16px;
/* 防止 iOS 在输入时自动放大 */
}
.container {
padding: 16px;
}
.grid {
gap: 12px;
}
.card {
padding: 12px;
}
.stat {
flex-direction: column;
gap: 4px;
min-width: 0;
}
.stat .label {
font-size: 11px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stat .value {
font-size: 15px;
line-height: 1.2;
white-space: nowrap;
}
.stat .badge {
padding: 2px 6px;
font-size: 13px;
width: fit-content;
}
.card .title {
flex-wrap: wrap;
}
.item .name {
max-width: 100px;
font-size: 14px;
}
.item .badge {
padding: 2px 4px;
font-size: 14px;
}
}
.up { color: var(--danger); }
.down { color: var(--success); }
.up {
color: var(--danger);
}
.down {
color: var(--success);
}
.list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
@media (max-width: 640px) {
.list { grid-template-columns: 1fr; }
.list {
grid-template-columns: 1fr;
}
}
.item {
@@ -293,6 +341,7 @@ input[type="number"] {
border: 1px solid var(--border);
background: #0b1220;
}
.item .name {
overflow: hidden;
white-space: nowrap;
@@ -306,6 +355,7 @@ input[type="number"] {
max-width: none;
word-break: break-word;
}
.item .weight {
font-weight: 600;
color: var(--accent);
@@ -359,18 +409,22 @@ input[type="number"] {
cursor: pointer;
transition: box-shadow 200ms ease, border-color 200ms ease, transform 150ms ease, color 200ms ease;
}
.icon-button:hover {
color: var(--text);
transform: translateY(-1px);
border-color: var(--accent);
}
.icon-button:active {
transform: translateY(0);
}
.icon-button.danger {
background: linear-gradient(180deg, #ef4444, #f87171);
color: #2b0b0b;
}
.icon-button.danger:hover {
box-shadow: 0 10px 20px rgba(248, 113, 113, 0.25);
}
@@ -385,9 +439,11 @@ input[type="number"] {
width: auto;
height: auto;
}
.fav-button:hover {
color: var(--accent);
}
.fav-button.active {
color: var(--accent);
}
@@ -456,8 +512,15 @@ input[type="number"] {
align-items: center;
}
.text-right { text-align: right; justify-content: flex-end; }
.text-center { text-align: center; justify-content: center; }
.text-right {
text-align: right;
justify-content: flex-end;
}
.text-center {
text-align: center;
justify-content: center;
}
.name-cell {
gap: 8px;
@@ -501,6 +564,7 @@ input[type="number"] {
.table-header-row {
display: none;
}
.table-row {
grid-template-columns: 1fr 80px 100px;
grid-template-areas:
@@ -509,23 +573,49 @@ input[type="number"] {
gap: 4px 12px;
padding: 12px !important;
}
.name-cell { grid-area: name; }
.value-cell { grid-area: value; }
.change-cell { grid-area: change; }
.profit-cell { grid-area: profit; }
.name-cell {
grid-area: name;
}
.value-cell {
grid-area: value;
}
.change-cell {
grid-area: change;
}
.profit-cell {
grid-area: profit;
}
.time-cell {
grid-area: time;
justify-content: flex-end;
}
.action-cell { display: none; }
.holding-cell { display: none; }
.holding-amount-cell { display: none; }
.action-cell {
display: none;
}
.holding-cell {
display: none;
}
.holding-amount-cell {
display: none;
}
.table-cell.time-cell span {
font-size: 10px !important;
}
}
.stat-compact .up { color: var(--danger); }
.stat-compact .up {
color: var(--danger);
}
@media (max-width: 768px) {
.action-cell .danger {
display: none;
@@ -534,7 +624,8 @@ input[type="number"] {
.swipe-action-bg {
position: absolute;
top: 1px; /* 留出一点缝隙,或者与 border 对齐 */
top: 1px;
/* 留出一点缝隙,或者与 border 对齐 */
bottom: 1px;
right: 0;
width: 80px;
@@ -551,7 +642,10 @@ input[type="number"] {
cursor: pointer;
box-shadow: inset 10px 0 20px -10px rgba(0,0,0,0.2); /* 增加一点内阴影,增加层次感 */
}
.stat-compact .down { color: var(--success); }
.stat-compact .down {
color: var(--success);
}
.filter-bar {
transition: all 0.3s ease;
@@ -572,9 +666,11 @@ input[type="number"] {
flex-direction: column;
align-items: center !important;
}
.tabs {
width: 100%;
justify-content: flex-start; /* 移动端改为左对齐 */
justify-content: flex-start;
/* 移动端改为左对齐 */
background: transparent;
border-radius: 0;
backdrop-filter: none;
@@ -626,16 +722,19 @@ input[type="number"] {
justify-content: center;
z-index: 60;
}
.modal {
width: 560px;
max-width: 92vw;
padding: 16px;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
display: inline-flex;
align-items: center;
@@ -1056,3 +1155,183 @@ input[type="number"] {
max-width: none !important;
}
}
/* ========== 用户菜单样式 ========== */
.user-menu-container {
position: relative;
}
.user-menu-trigger {
position: relative;
}
.user-menu-trigger.logged-in {
border-color: var(--primary);
background: rgba(34, 211, 238, 0.1);
}
.user-avatar-small {
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary), var(--accent));
color: #05263b;
font-size: 11px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.user-menu-dropdown {
position: fixed;
top: 76px;
right: 16px;
min-width: 200px;
padding: 8px;
z-index: 100;
background: rgba(15, 23, 42, 0.95);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
}
.user-menu-header {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 8px;
}
.user-avatar-large {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary), var(--accent));
color: #05263b;
font-size: 18px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.user-info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.user-email {
font-size: 13px;
font-weight: 600;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-status {
font-size: 11px;
color: var(--success);
}
.user-menu-divider {
height: 1px;
background: var(--border);
margin: 4px 0;
}
.user-menu-item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 12px;
border: none;
border-radius: 10px;
background: transparent;
color: var(--text);
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.user-menu-item:hover {
background: rgba(255, 255, 255, 0.08);
}
.user-menu-item.danger {
color: var(--danger);
}
.user-menu-item.danger:hover {
background: rgba(248, 113, 113, 0.1);
}
/* ========== 登录模态框样式 ========== */
.login-modal {
max-width: 400px !important;
}
.login-message {
padding: 10px 14px;
border-radius: 10px;
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
}
.login-message.error {
background: rgba(248, 113, 113, 0.15);
border: 1px solid rgba(248, 113, 113, 0.3);
color: var(--danger);
}
.login-message.success {
background: rgba(52, 211, 153, 0.15);
border: 1px solid rgba(52, 211, 153, 0.3);
color: var(--success);
}
.button.secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
.button.secondary:hover {
border-color: var(--muted);
background: rgba(255, 255, 255, 0.05);
box-shadow: none;
}
/* ========== 移动端响应式 ========== */
@media (max-width: 640px) {
.user-menu-dropdown {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
min-width: 100%;
border-radius: 20px 20px 0 0;
padding: 16px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
.user-menu-header {
padding: 16px 8px;
}
.user-menu-item {
padding: 14px 12px;
font-size: 15px;
}
.login-modal {
max-width: 100% !important;
margin: 16px;
}
}

View File

@@ -6,6 +6,7 @@ import Announcement from "./components/Announcement";
import zhifubaoImg from "./assets/zhifubao.jpg";
import weixinImg from "./assets/weixin.jpg";
import githubImg from "./assets/github.svg";
import { supabase } from './lib/supabase';
function PlusIcon(props) {
return (
@@ -62,6 +63,44 @@ function SortIcon(props) {
);
}
function UserIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="8" r="4" stroke="currentColor" strokeWidth="2" />
<path d="M4 20c0-4 4-6 8-6s8 2 8 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}
function LogoutIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<polyline points="16 17 21 12 16 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function LoginIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<polyline points="10 17 15 12 10 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<line x1="15" y1="12" x2="3" y2="12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function MailIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<rect x="2" y="4" width="20" height="16" rx="2" stroke="currentColor" strokeWidth="2" />
<path d="M22 6l-10 7L2 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function GridIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
@@ -1686,6 +1725,15 @@ export default function HomePage() {
// 视图模式
const [viewMode, setViewMode] = useState('card'); // card, list
// 用户认证状态
const [user, setUser] = useState(null);
const [userMenuOpen, setUserMenuOpen] = useState(false);
const [loginModalOpen, setLoginModalOpen] = useState(false);
const [loginEmail, setLoginEmail] = useState('');
const [loginLoading, setLoginLoading] = useState(false);
const [loginError, setLoginError] = useState('');
const [loginSuccess, setLoginSuccess] = useState('');
// 反馈弹窗状态
const [feedbackOpen, setFeedbackOpen] = useState(false);
const [feedbackNonce, setFeedbackNonce] = useState(0);
@@ -2173,6 +2221,91 @@ export default function HomePage() {
} catch { }
}, []);
// 初始化认证状态监听
useEffect(() => {
// 获取当前 session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
});
// 监听认证状态变化
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
if (session?.user) {
setLoginModalOpen(false);
setLoginEmail('');
setLoginSuccess('');
setLoginError('');
}
});
return () => subscription.unsubscribe();
}, []);
// 发送魔术链接邮件
const handleSendMagicLink = async (e) => {
e.preventDefault();
setLoginError('');
setLoginSuccess('');
// 简单的邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!loginEmail.trim()) {
setLoginError('请输入邮箱地址');
return;
}
if (!emailRegex.test(loginEmail.trim())) {
setLoginError('请输入有效的邮箱地址');
return;
}
setLoginLoading(true);
try {
const { error } = await supabase.auth.signInWithOtp({
email: loginEmail.trim(),
options: {
emailRedirectTo: window.location.origin
}
});
if (error) throw error;
setLoginSuccess('验证邮件已发送,请查收邮箱并点击链接完成登录');
} catch (err) {
if (err.message?.includes('rate limit')) {
setLoginError('请求过于频繁,请稍后再试');
} else if (err.message?.includes('network')) {
setLoginError('网络错误,请检查网络连接');
} else {
setLoginError(err.message || '发送验证邮件失败,请稍后再试');
}
} finally {
setLoginLoading(false);
}
};
// 登出
const handleLogout = async () => {
try {
await supabase.auth.signOut();
setUserMenuOpen(false);
} catch (err) {
console.error('登出失败', err);
}
};
// 关闭用户菜单(点击外部时)
const userMenuRef = useRef(null);
useEffect(() => {
const handleClickOutside = (event) => {
if (userMenuRef.current && !userMenuRef.current.contains(event.target)) {
setUserMenuOpen(false);
}
};
if (userMenuOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [userMenuOpen]);
useEffect(() => {
if (timerRef.current) clearInterval(timerRef.current);
timerRef.current = setInterval(() => {
@@ -2994,6 +3127,89 @@ export default function HomePage() {
>
<SettingsIcon width="18" height="18" />
</button>
{/* 临时隐藏用户菜单入口 */}
<div className="user-menu-container" ref={userMenuRef} hidden>
<button
className={`icon-button user-menu-trigger ${user ? 'logged-in' : ''}`}
aria-label={user ? '用户菜单' : '登录'}
onClick={() => setUserMenuOpen(!userMenuOpen)}
title={user ? (user.email || '用户') : '用户菜单'}
>
{user ? (
<div className="user-avatar-small">
{user.email?.charAt(0).toUpperCase() || 'U'}
</div>
) : (
<UserIcon width="18" height="18" />
)}
</button>
<AnimatePresence>
{userMenuOpen && (
<motion.div
className="user-menu-dropdown glass"
initial={{ opacity: 0, y: -10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{ duration: 0.15 }}
style={{ transformOrigin: 'top right' }}
>
{user ? (
<>
<div className="user-menu-header">
<div className="user-avatar-large">
{user.email?.charAt(0).toUpperCase() || 'U'}
</div>
<div className="user-info">
<span className="user-email">{user.email}</span>
<span className="user-status">已登录</span>
</div>
</div>
<div className="user-menu-divider" />
<button
className="user-menu-item"
onClick={() => {
setUserMenuOpen(false);
setSettingsOpen(true);
}}
>
<SettingsIcon width="16" height="16" />
<span>设置</span>
</button>
<button className="user-menu-item danger" onClick={handleLogout}>
<LogoutIcon width="16" height="16" />
<span>登出</span>
</button>
</>
) : (
<>
<button
className="user-menu-item"
onClick={() => {
setUserMenuOpen(false);
setLoginModalOpen(true);
}}
>
<LoginIcon width="16" height="16" />
<span>登录</span>
</button>
<button
className="user-menu-item"
onClick={() => {
setUserMenuOpen(false);
setSettingsOpen(true);
}}
>
<SettingsIcon width="16" height="16" />
<span>设置</span>
</button>
</>
)}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
@@ -4061,6 +4277,82 @@ export default function HomePage() {
</div>
</div>
)}
{/* 登录模态框 */}
{loginModalOpen && (
<div
className="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="登录"
onClick={() => {
setLoginModalOpen(false);
setLoginError('');
setLoginSuccess('');
setLoginEmail('');
}}
>
<div className="glass card modal login-modal" onClick={(e) => e.stopPropagation()}>
<div className="title" style={{ marginBottom: 16 }}>
<MailIcon width="20" height="20" />
<span>邮箱登录</span>
<span className="muted">使用邮箱验证登录</span>
</div>
<form onSubmit={handleSendMagicLink}>
<div className="form-group" style={{ marginBottom: 16 }}>
<div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>
输入您的邮箱地址我们将发送一封验证邮件
</div>
<input
className="input"
type="email"
placeholder="your@email.com"
value={loginEmail}
onChange={(e) => setLoginEmail(e.target.value)}
disabled={loginLoading}
autoFocus
/>
</div>
{loginError && (
<div className="login-message error" style={{ marginBottom: 12 }}>
<span>{loginError}</span>
</div>
)}
{loginSuccess && (
<div className="login-message success" style={{ marginBottom: 12 }}>
<span>{loginSuccess}</span>
</div>
)}
<div className="row" style={{ justifyContent: 'flex-end', gap: 12 }}>
<button
type="button"
className="button secondary"
onClick={() => {
setLoginModalOpen(false);
setLoginError('');
setLoginSuccess('');
setLoginEmail('');
}}
disabled={loginLoading}
>
取消
</button>
<button
className="button"
type="submit"
disabled={loginLoading || loginSuccess}
>
{loginLoading ? '发送中...' : loginSuccess ? '已发送' : '发送验证邮件'}
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
}

153
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "real-time-fund",
"version": "0.1.0",
"dependencies": {
"@supabase/supabase-js": "^2.78.0",
"framer-motion": "^12.29.2",
"next": "14.2.5",
"react": "18.3.1",
@@ -164,6 +165,85 @@
"node": ">= 10"
}
},
"node_modules/@supabase/auth-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/auth-js/-/auth-js-2.78.0.tgz",
"integrity": "sha512-cXDtu1U0LeZj/xfnFoV7yCze37TcbNo8FCxy1FpqhMbB9u9QxxDSW6pA5gm/07Ei7m260Lof4CZx67Cu6DPeig==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"tslib": "2.8.1"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/functions-js/-/functions-js-2.78.0.tgz",
"integrity": "sha512-t1jOvArBsOINyqaRee1xJ3gryXLvkBzqnKfi6q3YRzzhJbGS6eXz0pXR5fqmJeB01fLC+1njpf3YhMszdPEF7g==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"tslib": "2.8.1"
}
},
"node_modules/@supabase/node-fetch": {
"version": "2.6.15",
"resolved": "https://registry.npmmirror.com/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/postgrest-js/-/postgrest-js-2.78.0.tgz",
"integrity": "sha512-AwhpYlSvJ+PSnPmIK8sHj7NGDyDENYfQGKrMtpVIEzQA2ApUjgpUGxzXWN4Z0wEtLQsvv7g4y9HVad9Hzo1TNA==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"tslib": "2.8.1"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/realtime-js/-/realtime-js-2.78.0.tgz",
"integrity": "sha512-rCs1zmLe7of7hj4s7G9z8rTqzWuNVtmwDr3FiCRCJFawEoa+RQO1xpZGbdeuVvVmKDyVN6b542Okci+117y/LQ==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"@types/phoenix": "^1.6.6",
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
}
},
"node_modules/@supabase/storage-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/storage-js/-/storage-js-2.78.0.tgz",
"integrity": "sha512-n17P0JbjHOlxqJpkaGFOn97i3EusEKPEbWOpuk1r4t00Wg06B8Z4GUiq0O0n1vUpjiMgJUkLIMuBVp+bEgunzQ==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "2.6.15",
"tslib": "2.8.1"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.78.0",
"resolved": "https://registry.npmmirror.com/@supabase/supabase-js/-/supabase-js-2.78.0.tgz",
"integrity": "sha512-xYMRNBFmKp2m1gMuwcp/gr/HlfZKqjye1Ib8kJe29XJNsgwsfO/f8skxnWiscFKTlkOKLuBexNgl5L8dzGt6vA==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.78.0",
"@supabase/functions-js": "2.78.0",
"@supabase/node-fetch": "2.6.15",
"@supabase/postgrest-js": "2.78.0",
"@supabase/realtime-js": "2.78.0",
"@supabase/storage-js": "2.78.0"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -180,6 +260,30 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "25.2.1",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.2.1.tgz",
"integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/phoenix": {
"version": "1.6.7",
"resolved": "https://registry.npmmirror.com/@types/phoenix/-/phoenix-1.6.7.tgz",
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -460,11 +564,60 @@
}
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View File

@@ -8,6 +8,7 @@
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^2.78.0",
"framer-motion": "^12.29.2",
"next": "14.2.5",
"react": "18.3.1",