Skip to content

Commit

Permalink
"Update User model, fix auth issues, and update README"
Browse files Browse the repository at this point in the history
  • Loading branch information
while-basic committed Dec 5, 2024
1 parent 7ea999a commit b3ab947
Show file tree
Hide file tree
Showing 23 changed files with 1,209 additions and 52 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ NEXT_PUBLIC_GA_ID=
NEXT_PUBLIC_SITE_URL=

# Admin page
ADMIN_PASSWORD=
ADMIN_PASSWORD=

# User Dashboard
WEATHER_API_KEY=
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
# Todo List
- [ ] Integrate bitcoin price api
- [ ] curl --location 'https://api.coindesk.com/v1/bpi/currentprice.json'
- [ ] add web3 login
- [ ] interactive art gallery where users can share their creations, uploads, and view others' work
- [ ] add a comments section for images
- [ ] use vercel ai sdk for chat and streaming
- [ ] add a chatbot for signed-in users
- [ ] add a dashboard for signed-in users (in progress)
- [ ] add a secret audio page
- [ ] add a resume page with ai podcast clips
- [ ] add daily ai tip or fact dialog alert when user signs in
- [ ] include token usage metrics in dashboard
- [ ] total conversations
- [ ] total characters generated
- [ ] total generations
- [ ] total cost
- [ ] remaining monthly quota
- [ ] show graphs or progress bars for better visualization
- [ ] add gallery for generated images to navlinks
- [ ] add shortcuts for frequently used tools
- [ ] Start a New Chat
- [ ] Generate an Image


# Christopher Celaya Portfolio

A modern, responsive portfolio website built with Next.js 14, Tailwind CSS, and shadcn/ui.
Expand Down
55 changes: 55 additions & 0 deletions app/api/analytics/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth-options'
import { Session } from 'next-auth'

type AnalyticsItem = {
date: Date
userCount: number
}

export async function GET() {
try {
const session = await getServerSession(authOptions) as Session | null
if (!session || !session.user || !session.user.email) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

// Get analytics data for the last 6 months
const sixMonthsAgo = new Date()
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6)

const analytics = await prisma.analytics.findMany({
where: {
date: {
gte: sixMonthsAgo,
},
},
orderBy: {
date: 'asc',
},
}) as AnalyticsItem[]

// Format data for chart
const data = {
labels: analytics.map(item => {
const date = new Date(item.date)
return `${date.getMonth() + 1}/${date.getFullYear()}`
}),
datasets: [
{
label: 'Users',
data: analytics.map(item => item.userCount),
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.5)',
}
],
}

return NextResponse.json(data)
} catch (error) {
console.error('Error fetching analytics:', error)
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}
}
43 changes: 43 additions & 0 deletions app/api/weather/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const location = searchParams.get('location');

if (!location) {
return NextResponse.json(
{ error: 'Location parameter is required' },
{ status: 400 }
);
}

try {
// Replace with your actual weather API key and endpoint
const WEATHER_API_KEY = process.env.WEATHER_API_KEY;
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${WEATHER_API_KEY}&q=${location}&aqi=no`
);

if (!response.ok) {
throw new Error('Weather API request failed');
}

const data = await response.json();

// Transform the API response to match our interface
const weatherData = {
temperature: data.current.temp_c,
condition: data.current.condition.text,
humidity: data.current.humidity,
windSpeed: data.current.wind_kph,
};

return NextResponse.json(weatherData);
} catch (error) {
console.error('Weather API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch weather data' },
{ status: 500 }
);
}
}
7 changes: 6 additions & 1 deletion app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { withClientBoundary } from "@/components/client-wrapper"
import Link from "next/link"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { FileTextIcon, MessageSquare, SettingsIcon } from "lucide-react"
import DashboardGrid from "@/components/dashboard/dashboard-grid"

function DashboardPage() {
const { user } = useAuth()
Expand All @@ -24,7 +25,8 @@ function DashboardPage() {
</div>
</div>

<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{/* Quick Access Cards */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 mb-8">
<Link href="/ai-editor">
<Card className="hover:bg-accent/50 transition-colors cursor-pointer h-full">
<CardHeader>
Expand Down Expand Up @@ -82,6 +84,9 @@ function DashboardPage() {
</Card>
</Link>
</div>

{/* Dashboard Widgets */}
<DashboardGrid />
</div>
</ProtectedRoute>
)
Expand Down
106 changes: 93 additions & 13 deletions components/dashboard/analytics-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { useEffect, useState, useCallback } from "react"
import { Card } from "@/components/ui/card"
import { Line } from "react-chartjs-2"
import {
Expand All @@ -12,6 +13,8 @@ import {
Tooltip,
Legend,
} from "chart.js"
import { toast } from "sonner"
import { createClient } from '@/lib/supabase/client'

ChartJS.register(
CategoryScale,
Expand All @@ -23,20 +26,76 @@ ChartJS.register(
Legend
)

interface AnalyticsChartProps {
data: {
labels: string[]
datasets: {
label: string
data: number[]
borderColor: string
backgroundColor: string
}[]
}
title: string
interface ChartData {
labels: string[]
datasets: {
label: string
data: number[]
borderColor: string
backgroundColor: string
}[]
}

export function AnalyticsChart({ data, title }: AnalyticsChartProps) {
export default function AnalyticsChart() {
const [data, setData] = useState<ChartData | null>(null);
const [loading, setLoading] = useState(true);
const supabase = createClient();

const fetchAnalytics = useCallback(async () => {
try {
// Get analytics data for the last 6 months
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);

const { data: analytics, error } = await supabase
.from('analytics')
.select('*')
.gte('date', sixMonthsAgo.toISOString())
.order('date', { ascending: true });

if (error) throw error;

// Format data for chart
const chartData = {
labels: analytics?.map(item => {
const date = new Date(item.date);
return `${date.getMonth() + 1}/${date.getFullYear()}`;
}) || [],
datasets: [
{
label: 'Users',
data: analytics?.map(item => item.user_count) || [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.5)',
},
{
label: 'Tasks',
data: analytics?.map(item => item.task_count) || [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
{
label: 'Events',
data: analytics?.map(item => item.event_count) || [],
borderColor: 'rgb(53, 162, 235)',
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};

setData(chartData);
} catch (error) {
console.error('Error:', error);
toast.error('Failed to fetch analytics');
} finally {
setLoading(false);
}
}, [supabase]);

useEffect(() => {
fetchAnalytics();
}, [fetchAnalytics]);

const options = {
responsive: true,
plugins: {
Expand All @@ -45,7 +104,7 @@ export function AnalyticsChart({ data, title }: AnalyticsChartProps) {
},
title: {
display: true,
text: title,
text: "Analytics Overview",
},
},
scales: {
Expand All @@ -55,6 +114,27 @@ export function AnalyticsChart({ data, title }: AnalyticsChartProps) {
},
}

if (loading) {
return (
<Card className="p-6">
<div className="animate-pulse space-y-4">
<div className="h-4 bg-zinc-800 rounded w-1/4"></div>
<div className="h-[300px] bg-zinc-800 rounded"></div>
</div>
</Card>
);
}

if (!data) {
return (
<Card className="p-6">
<div className="text-center text-muted-foreground">
No analytics data available
</div>
</Card>
);
}

return (
<Card className="p-6">
<Line options={options} data={data} />
Expand Down
48 changes: 48 additions & 0 deletions components/dashboard/dashboard-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client';

import React from 'react';
import AnalyticsChart from './analytics-chart';
import RecentUsers from './recent-users';
import StatsCard from './stats-card';
import { Users, FolderGit, BarChart3 } from 'lucide-react';

export default function DashboardGrid() {
return (
<div className="grid gap-6 p-6">
{/* Top Stats Row */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<StatsCard
title="Total Users"
value="1,234"
change="+12%"
trend="up"
icon={<Users className="w-4 h-4" />}
/>
<StatsCard
title="Active Projects"
value="56"
change="+8%"
trend="up"
icon={<FolderGit className="w-4 h-4" />}
/>
<StatsCard
title="Completion Rate"
value="94%"
change="+5%"
trend="up"
icon={<BarChart3 className="w-4 h-4" />}
/>
</div>

{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<AnalyticsChart />
</div>

{/* Bottom Row */}
<div className="grid grid-cols-1">
<RecentUsers />
</div>
</div>
);
}
28 changes: 26 additions & 2 deletions components/dashboard/recent-users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,34 @@ export interface User {
}

interface RecentUsersProps {
users: User[]
users?: User[]
}

export function RecentUsers({ users }: RecentUsersProps) {
// todo: replace user emails with first names only
const mockUsers: User[] = [
{
id: "1",
email: "[email protected]",
createdAt: "2024-01-01",
},
{
id: "2",
email: "[email protected]",
createdAt: "2024-01-02",
},
{
id: "3",
email: "[email protected]",
createdAt: "2024-01-03",
},
{
id: "4",
email: "[email protected]",
createdAt: "2024-01-04",
},
]

export default function RecentUsers({ users = mockUsers }: RecentUsersProps) {
return (
<Card className="p-6">
<h2 className="text-xl font-semibold mb-4">Recent Users</h2>
Expand Down
Loading

0 comments on commit b3ab947

Please sign in to comment.