Skip to content

Commit

Permalink
slightly updated page
Browse files Browse the repository at this point in the history
  • Loading branch information
yashchitneni committed Sep 5, 2024
1 parent 27dd793 commit 8bb1b77
Show file tree
Hide file tree
Showing 26 changed files with 1,370 additions and 58 deletions.
66 changes: 66 additions & 0 deletions actions/stripe-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
updateProfile,
updateProfileByCustomerId
} from "../db/queries/profiles-queries"
import { Membership } from "../types/membership"
import Stripe from "stripe"
import { stripe } from "../lib/stripe"

const getMembershipStatus = (
status: Stripe.Subscription.Status,
membership: Membership
): Membership => {
switch (status) {
case "active":
case "trialing":
return membership
case "canceled":
case "incomplete":
case "incomplete_expired":
case "past_due":
case "paused":
case "unpaid":
return "free"
default:
return "free"
}
}

export const updateStripeCustomer = async (
profileId: string,
subscriptionId: string,
customerId: string
) => {
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ["default_payment_method"]
})

const updatedProfile = await updateProfile(profileId, {
stripeCustomerId: customerId,
stripeSubscriptionId: subscription.id
})

if (!updatedProfile) {
throw new Error("Failed to update customer")
}
}

export const manageSubscriptionStatusChange = async (
subscriptionId: string,
customerId: string,
productId: string
) => {
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ["default_payment_method"]
})

const product = await stripe.products.retrieve(productId)
const membership = product.metadata.membership as Membership

const membershipStatus = getMembershipStatus(subscription.status, membership)

await updateProfileByCustomerId(customerId, {
stripeSubscriptionId: subscription.id,
membership: membershipStatus
})
}
13 changes: 0 additions & 13 deletions app/(root)/layout.tsx

This file was deleted.

90 changes: 90 additions & 0 deletions app/api/stripe/webhooks/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { stripe } from "../../../../lib/stripe"
import {
manageSubscriptionStatusChange,
updateStripeCustomer
} from "../../../../actions/stripe-actions"
import { headers } from "next/headers"
import Stripe from "stripe"

const relevantEvents = new Set([
"checkout.session.completed",
"customer.subscription.updated",
"customer.subscription.deleted"
])

export async function POST(req: Request) {
const body = await req.text()
const sig = headers().get("Stripe-Signature") as string
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET
let event: Stripe.Event

try {
if (!sig || !webhookSecret) {
throw new Error("Webhook secret or signature missing")
}

event = stripe.webhooks.constructEvent(body, sig, webhookSecret)
} catch (err: any) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 })
}

if (relevantEvents.has(event.type)) {
try {
switch (event.type) {
case "customer.subscription.updated":
case "customer.subscription.deleted":
const subscription = event.data.object as Stripe.Subscription
const productId = subscription.items.data[0].price.product as string

await manageSubscriptionStatusChange(
subscription.id,
subscription.customer as string,
productId
)

break

case "checkout.session.completed":
const checkoutSession = event.data.object as Stripe.Checkout.Session
if (checkoutSession.mode === "subscription") {
const subscriptionId = checkoutSession.subscription

await updateStripeCustomer(
checkoutSession.client_reference_id as string,
subscriptionId as string,
checkoutSession.customer as string
)

const subscription = await stripe.subscriptions.retrieve(
subscriptionId as string,
{
expand: ["default_payment_method"]
}
)

const productId = subscription.items.data[0].price.product as string

await manageSubscriptionStatusChange(
subscription.id,
subscription.customer as string,
productId
)
}

break

default:
throw new Error("Unhandled relevant event!")
}
} catch (error) {
return new Response(
"Webhook handler failed. View your nextjs function logs.",
{
status: 400
}
)
}
}

return new Response(JSON.stringify({ received: true }))
}
68 changes: 68 additions & 0 deletions app/biofeedback/BiofeedbackChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client'

import { useEffect, useState } from 'react'
import { createClient } from '@supabase/supabase-js'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'

// Initialize Supabase client
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

interface BiofeedbackEntry {
date: string
hunger_score: number
digestion_score: number
sleep_quality_score: number
energy_levels_score: number
gym_performance_score: number | null
}

export default function BiofeedbackChart() {
const [data, setData] = useState<BiofeedbackEntry[]>([])

useEffect(() => {
const fetchData = async () => {
const { data, error } = await supabase
.from('biofeedback')
.select('date, hunger_score, digestion_score, sleep_quality_score, energy_levels_score, gym_performance_score')
.order('date', { ascending: true })

if (error) {
console.error('Error fetching data:', error)
} else {
setData(data)
}
}

fetchData()

// Set up real-time listener
const subscription = supabase
.channel('biofeedback_changes')
.on('postgres_changes', { event: '*', schema: 'public', table: 'biofeedback' }, fetchData)
.subscribe()

return () => {
subscription.unsubscribe()
}
}, [])

return (
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis domain={[0, 10]} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="hunger_score" stroke="#8884d8" />
<Line type="monotone" dataKey="digestion_score" stroke="#82ca9d" />
<Line type="monotone" dataKey="sleep_quality_score" stroke="#ffc658" />
<Line type="monotone" dataKey="energy_levels_score" stroke="#ff7300" />
<Line type="monotone" dataKey="gym_performance_score" stroke="#00C49F" />
</LineChart>
</ResponsiveContainer>
)
}
10 changes: 10 additions & 0 deletions app/biofeedback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import BiofeedbackChart from './BiofeedbackChart'

export default function BiofeedbackPage() {
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Biofeedback Tracking</h1>
<BiofeedbackChart />
</div>
)
}
9 changes: 9 additions & 0 deletions app/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ButtonHTMLAttributes, ReactNode } from 'react';

export const Button = ({ children, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & { children: ReactNode }) => {
return (
<button {...props}>
{children}
</button>
);
};
5 changes: 5 additions & 0 deletions app/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react'; // {{ edit_1 }}

export function Input({ ...props }) {
return <input {...props} className="input" />
}
92 changes: 92 additions & 0 deletions app/dashboard/JsonUploadForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client'

import { useState } from 'react'
import { Button } from '@/app/components/ui/button'
import { createClient } from '@supabase/supabase-js'
import { Input } from '@/app/components/ui/input'
import { useAuth } from '@clerk/nextjs'

// Initialize Supabase client
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

interface BiofeedbackData {
date: string
time: string
metrics: {
hunger_levels: { score: number; notes: string }
digestion: { score: number; notes: string }
sleep_quality: { score: number; notes: string }
energy_levels: { score: number; notes: string }
gym_performance: { score: number | null; notes: string }
}
additional_notes: string[]
summary: string
}

export default function JsonUploadForm() {
const { getToken } = useAuth()
const [jsonData, setJsonData] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)

try {
const token = await getToken({ template: 'supabase' })
if (!token) throw new Error('Not authenticated')

supabase.auth.setAuth(token)

const data: BiofeedbackData = JSON.parse(jsonData)

const { error } = await supabase.from('biofeedback').insert({
date: new Date(`${data.date}T${data.time}`),
time: new Date(`${data.date}T${data.time}`),
hunger_score: data.metrics.hunger_levels.score,
hunger_notes: data.metrics.hunger_levels.notes,
digestion_score: data.metrics.digestion.score,
digestion_notes: data.metrics.digestion.notes,
sleep_quality_score: data.metrics.sleep_quality.score,
sleep_quality_notes: data.metrics.sleep_quality.notes,
energy_levels_score: data.metrics.energy_levels.score,
energy_levels_notes: data.metrics.energy_levels.notes,
gym_performance_score: data.metrics.gym_performance.score,
gym_performance_notes: data.metrics.gym_performance.notes,
additional_notes: data.additional_notes,
summary: data.summary,
})

if (error) throw error

setJsonData('')
alert('Data uploaded successfully!')
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred')
} finally {
setIsLoading(false)
}
}

return (
<form onSubmit={handleSubmit} className="space-y-4">
<textarea
value={jsonData}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setJsonData(e.target.value)}
placeholder="Paste your JSON data here"
rows={10}
className="w-full p-2 border rounded"
required
/>
<Button type="submit" disabled={isLoading}>
{isLoading ? 'Uploading...' : 'Upload Data'}
</Button>
{error && <p className="text-red-500">{error}</p>}
</form>
)
}
Loading

0 comments on commit 8bb1b77

Please sign in to comment.