diff --git a/package.json b/package.json index e69e36a..df7f1e2 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@graphprotocol/graph-cli": "^0.91.1", "ethereum-blockies": "^0.1.1", "framer-motion": "^11.13.1", - "graphql": "^16.9.0" + "graphql": "^16.9.0", + "next-auth": "beta" } } diff --git a/packages/nextjs/app/layout.tsx b/packages/nextjs/app/layout.tsx index 468402c..dd92149 100644 --- a/packages/nextjs/app/layout.tsx +++ b/packages/nextjs/app/layout.tsx @@ -1,11 +1,80 @@ +"use client"; + +import { useEffect } from "react"; +import axios from "axios"; import "@rainbow-me/rainbowkit/styles.css"; import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders"; import { ThemeProvider } from "~~/components/ThemeProvider"; import { ApolloProvider } from "~~/context/ApolloProvider"; +import { FitnessProvider, useFitness } from "~~/context/FitnessContext"; import "~~/styles/globals.css"; -import { getMetadata } from "~~/utils/scaffold-eth/getMetadata"; -export const metadata = getMetadata({ title: "Scaffold-ETH 2 App", description: "Built with 🏗 Scaffold-ETH 2" }); +const TokenHandler = ({ children }: { children: React.ReactNode }) => { + const { setFitnessData, setAccessToken } = useFitness(); + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const accessToken = urlParams.get('access_token'); + const idToken = urlParams.get('id_token'); + const refreshToken = urlParams.get('refresh_token'); + + if (accessToken || idToken || refreshToken) { + console.log('Access Token:', accessToken); + console.log('ID Token:', idToken); + console.log('Refresh Token:', refreshToken); + + localStorage.setItem('access_token', accessToken || ''); + localStorage.setItem('id_token', idToken || ''); + localStorage.setItem('refresh_token', refreshToken || ''); + + if (accessToken) { + setAccessToken(accessToken); + } + + // Fetch fitness data + const fetchFitnessData = async () => { + // Get today's start and end timestamps + const now = new Date(); + const startOfDay = new Date(now.setHours(0, 0, 0, 0)).getTime(); + const endOfDay = new Date(now.setHours(23, 59, 59, 999)).getTime(); + + const data = { + aggregateBy: [ + { + dataTypeName: "com.google.step_count.delta" + } + ], + startTimeMillis: startOfDay, + endTimeMillis: endOfDay, + bucketByTime: { + durationMillis: 86400000 // 24 hours in milliseconds + } + }; + + try { + const response = await axios.post( + 'https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', + data, + { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + console.log('Fitness Data:', JSON.stringify(response.data)); + setFitnessData(response.data); + } catch (error) { + console.error('Fitness API Error:', error); + } + }; + + fetchFitnessData(); + } + }, [setFitnessData, setAccessToken]); + + return <>{children}; +}; const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { return ( @@ -13,7 +82,13 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { - {children} + + + + {children} + + + @@ -21,4 +96,4 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { ); }; -export default ScaffoldEthApp; +export default ScaffoldEthApp; \ No newline at end of file diff --git a/packages/nextjs/app/login/page.tsx b/packages/nextjs/app/login/page.tsx new file mode 100644 index 0000000..440367c --- /dev/null +++ b/packages/nextjs/app/login/page.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { useState } from "react"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { motion } from "framer-motion"; + +const LoginPage = () => { + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + + const handleSignUp = () => { + setIsLoading(true); + window.location.href = "https://small-mouse-2759.arnabbhowmik019.workers.dev/google/auth?redirect_url=http%3A%2F%2Flocalhost%3A3000/"; + }; + + const handleDemoLogin = () => { + setIsLoading(true); + // Simulate loading + setTimeout(() => { + router.push("/"); + setIsLoading(false); + }, 1000); + }; + + return ( +
+ + {/* Logo and Title */} +
+ + StakeFIT Logo + +

Welcome to StakeFIT

+

Stake your health, earn rewards

+
+ + {/* Login Card */} + +
+ {/* Demo Account Button */} + +
+ + {/* Terms */} +

+ By continuing, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + +

+
+
+
+ ); +}; + +export default LoginPage; \ No newline at end of file diff --git a/packages/nextjs/app/metadata.ts b/packages/nextjs/app/metadata.ts new file mode 100644 index 0000000..15fc28f --- /dev/null +++ b/packages/nextjs/app/metadata.ts @@ -0,0 +1,3 @@ +import { getMetadata } from "~~/utils/scaffold-eth/getMetadata"; + +export const metadata = getMetadata({ title: "Momentum", description: "Built with 🏗 Scaffold-ETH 2" }); \ No newline at end of file diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index b0992dd..e0302bc 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -19,9 +19,9 @@ const Home: NextPage = () => { return ( <> {/* Floating Stake Button - Always visible */} -
+ {/*
-
+
*/}
@@ -37,11 +37,11 @@ const Home: NextPage = () => {
- +
- +
{/* Stake Card with CTA Arrow */} diff --git a/packages/nextjs/components/ChatSearchBar.tsx b/packages/nextjs/components/ChatSearchBar.tsx index 7937be6..654e05e 100644 --- a/packages/nextjs/components/ChatSearchBar.tsx +++ b/packages/nextjs/components/ChatSearchBar.tsx @@ -1,5 +1,6 @@ -import { FC, useState } from "react"; -import { MagnifyingGlassIcon, PaperAirplaneIcon, XMarkIcon } from "@heroicons/react/24/outline"; +import { FC, useState, useEffect } from "react"; +import { MagnifyingGlassIcon, PaperAirplaneIcon, XMarkIcon, TrophyIcon } from "@heroicons/react/24/outline"; +import { motion, AnimatePresence } from "framer-motion"; interface Message { text: string; @@ -10,6 +11,34 @@ const ChatSearchBar: FC = () => { const [isChat, setIsChat] = useState(false); const [inputText, setInputText] = useState(""); const [messages, setMessages] = useState([]); + const [showCTA, setShowCTA] = useState(false); + + useEffect(() => { + // Show CTA after 2 seconds + const timer = setTimeout(() => setShowCTA(true), 2000); + return () => clearTimeout(timer); + }, []); + + const dailyChallenges = `Today's Challenges 🏋️‍♂️: +1. 10 Push-ups +2. 20 Sit-ups +3. 30 Jumping Jacks +4. 1 minute Plank +5. 20 Squats + +Complete these exercises to earn 0.01 ETH! +Reply 'done' when you've completed the challenge.`; + + const handleDailyChallenges = () => { + setIsChat(true); + setShowCTA(false); + setMessages([ + { + text: dailyChallenges, + isUser: false, + }, + ]); + }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -17,12 +46,19 @@ const ChatSearchBar: FC = () => { setMessages(prev => [...prev, { text: inputText, isUser: true }]); - // Simulate bot response (replace with actual bot logic) + // Bot response logic setTimeout(() => { + let botResponse = "This is a sample response from the bot."; + + // Check if user completed challenge + if (inputText.toLowerCase() === "done") { + botResponse = "Congratulations! 🎉 You've completed today's challenge. Your reward of 0.01 ETH will be processed shortly."; + } + setMessages(prev => [ ...prev, { - text: "This is a sample response from the bot.", + text: botResponse, isUser: false, }, ]); @@ -32,63 +68,125 @@ const ChatSearchBar: FC = () => { }; return ( -
- {/* Header when in chat mode */} - {isChat && ( -
-

Chat Assistant

- -
- )} - - {/* Chat Messages */} -
- {messages.map((message, index) => ( -
-
+ {/* Pointing Arrow CTA */} + + {showCTA && !isChat && ( + + - {message.text} -
-
- ))} -
+ {/* Glow effect */} +
- {/* Input Bar */} -
-
-
- setInputText(e.target.value)} - onFocus={() => setIsChat(true)} - className="w-full px-4 py-2 rounded-xl bg-[#000001] text-[#fbf8fe] - placeholder-[#a3a2a7] focus:outline-none focus:ring-2 focus:ring-[#11ce6f]" - /> - {!isChat && ( - - )} +
+

+ Daily Challenge is here! 💪 +

+ {/* Arrow pointing down */} +
+
+
+
+ + + )} + + + {/* Chat Interface */} +
+ {/* Chat Header */} + {isChat && ( +
+

Chat Assistant

+
- {isChat && ( + )} + + {/* Chat Messages */} +
+ {messages.map((message, index) => ( +
+
+ {message.text.split('\n').map((line, i) => ( +
{line}
+ ))} +
+
+ ))} +
+ + {/* Input Bar */} +
+ + {/* Daily Challenges Button */} - )} - + +
+ setInputText(e.target.value)} + onFocus={() => setIsChat(true)} + className="w-full px-4 py-2 rounded-xl bg-[#000001] text-[#fbf8fe] + placeholder-[#a3a2a7] focus:outline-none focus:ring-2 focus:ring-[#11ce6f]" + /> + {!isChat && ( + + )} +
+ {isChat && ( + + )} + +
-
+ ); }; -export default ChatSearchBar; +export default ChatSearchBar; \ No newline at end of file diff --git a/packages/nextjs/components/StatsComponent.tsx b/packages/nextjs/components/StatsComponent.tsx index 8c3548d..694eeab 100644 --- a/packages/nextjs/components/StatsComponent.tsx +++ b/packages/nextjs/components/StatsComponent.tsx @@ -1,16 +1,28 @@ -import { FC } from "react"; +"use client"; + +import { FC, useEffect } from "react"; +import { useFitness } from "~~/context/FitnessContext"; interface StatsComponentProps { - steps: number; - minutes: number; - calories: number; stepsGoal?: number; } -const StatsComponent: FC = ({ steps, minutes, calories, stepsGoal = 6000 }) => { +const StatsComponent: FC = ({ stepsGoal = 6000 }) => { + const { fitnessData, fetchFitnessData } = useFitness(); + + useEffect(() => { + fetchFitnessData(); + const interval = setInterval(fetchFitnessData, 2 * 60 * 1000); + return () => clearInterval(interval); + }, [fetchFitnessData]); + + const steps = fitnessData?.bucket?.[0]?.dataset?.[0]?.point?.[0]?.value?.[0]?.intVal || 0; + const currentHour = new Date().getHours(); + const calories = Math.round(steps * 0.21); + return (
-
+
{/* Steps */}
@@ -20,12 +32,12 @@ const StatsComponent: FC = ({ steps, minutes, calories, ste
- {/* Minutes */} + {/* Hours */}
- {minutes} - mins + {currentHour} + hrs
@@ -39,10 +51,36 @@ const StatsComponent: FC = ({ steps, minutes, calories, ste
- {/* Heart Progress Indicator */} -
+ {/* Progress Circles */} +
- {/* Pink circle */} + {/* Background circles */} + + + + + {/* Progress circles */} = ({ steps, minutes, calories, ste fill="none" stroke="#ec4899" strokeWidth="8" - strokeDasharray={`${(calories / 100) * 251.2} 251.2`} - className="opacity-20" + strokeDasharray={`${(calories / 2000) * 251.2} 251.2`} + strokeLinecap="round" /> - {/* Blue circle */} = ({ steps, minutes, calories, ste fill="none" stroke="#3b82f6" strokeWidth="8" - strokeDasharray={`${(minutes / 30) * 188.4} 188.4`} - className="opacity-20" + strokeDasharray={`${(currentHour / 24) * 188.4} 188.4`} + strokeLinecap="round" /> - {/* Green circle */} = ({ steps, minutes, calories, ste stroke="#11ce6f" strokeWidth="8" strokeDasharray={`${(steps / stepsGoal) * 125.6} 125.6`} - className="opacity-20" + strokeLinecap="round" />
@@ -81,4 +117,4 @@ const StatsComponent: FC = ({ steps, minutes, calories, ste ); }; -export default StatsComponent; +export default StatsComponent; \ No newline at end of file diff --git a/packages/nextjs/components/StepComponent.tsx b/packages/nextjs/components/StepComponent.tsx index 61c1bd1..27e1feb 100644 --- a/packages/nextjs/components/StepComponent.tsx +++ b/packages/nextjs/components/StepComponent.tsx @@ -1,11 +1,23 @@ -import { FC } from "react"; +"use client"; + +import { FC, useEffect } from "react"; +import { useFitness } from "~~/context/FitnessContext"; interface StepComponentProps { - currentSteps: number; - totalSteps: number; + totalSteps?: number; } -const StepComponent: FC = ({ currentSteps, totalSteps }) => { +const StepComponent: FC = ({ totalSteps = 6000 }) => { + const { fitnessData, fetchFitnessData } = useFitness(); + + useEffect(() => { + fetchFitnessData(); + const interval = setInterval(fetchFitnessData, 2 * 60 * 1000); + return () => clearInterval(interval); + }, [fetchFitnessData]); + + // Extract current steps from fitness data + const currentSteps = fitnessData?.bucket?.[0]?.dataset?.[0]?.point?.[0]?.value?.[0]?.intVal || 0; const percentage = Math.round((currentSteps / totalSteps) * 100); return ( @@ -36,4 +48,4 @@ const StepComponent: FC = ({ currentSteps, totalSteps }) => ); }; -export default StepComponent; +export default StepComponent; \ No newline at end of file diff --git a/packages/nextjs/context/FitnessContext.tsx b/packages/nextjs/context/FitnessContext.tsx new file mode 100644 index 0000000..5370477 --- /dev/null +++ b/packages/nextjs/context/FitnessContext.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { createContext, useContext, useState, useCallback } from 'react'; +import axios from 'axios'; + +interface FitnessContextType { + fitnessData: any; + setFitnessData: (data: any) => void; // Add this line + fetchFitnessData: () => Promise; + accessToken: string | null; + setAccessToken: (token: string | null) => void; +} + +const FitnessContext = createContext(undefined); + +export function FitnessProvider({ children }: { children: React.ReactNode }) { + const [fitnessData, setFitnessData] = useState(null); + const [accessToken, setAccessToken] = useState(null); + + const fetchFitnessData = useCallback(async () => { + if (!accessToken) return; + + const now = new Date(); + const startOfDay = new Date(now.setHours(0, 0, 0, 0)).getTime(); + const endOfDay = new Date(now.setHours(23, 59, 59, 999)).getTime(); + + const data = { + aggregateBy: [{ + dataTypeName: "com.google.step_count.delta" + }], + startTimeMillis: startOfDay, + endTimeMillis: endOfDay, + bucketByTime: { + durationMillis: 86400000 + } + }; + + try { + const response = await axios.post( + 'https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', + data, + { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + setFitnessData(response.data); + } catch (error) { + console.error('Error fetching fitness data:', error); + } + }, [accessToken]); + + return ( + + {children} + + ); +} + +export function useFitness() { + const context = useContext(FitnessContext); + if (context === undefined) { + throw new Error('useFitness must be used within a FitnessProvider'); + } + return context; +} \ No newline at end of file diff --git a/packages/nextjs/public/dalla.webp b/packages/nextjs/public/dalla.webp new file mode 100644 index 0000000..989175c Binary files /dev/null and b/packages/nextjs/public/dalla.webp differ diff --git a/packages/nextjs/public/logo.png b/packages/nextjs/public/logo.png new file mode 100644 index 0000000..565fe61 Binary files /dev/null and b/packages/nextjs/public/logo.png differ diff --git a/yarn.lock b/yarn.lock index 1e0db38..e5129f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -143,6 +143,32 @@ __metadata: languageName: node linkType: hard +"@auth/core@npm:0.37.2": + version: 0.37.2 + resolution: "@auth/core@npm:0.37.2" + dependencies: + "@panva/hkdf": ^1.2.1 + "@types/cookie": 0.6.0 + cookie: 0.7.1 + jose: ^5.9.3 + oauth4webapi: ^3.0.0 + preact: 10.11.3 + preact-render-to-string: 5.2.3 + peerDependencies: + "@simplewebauthn/browser": ^9.0.1 + "@simplewebauthn/server": ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + "@simplewebauthn/browser": + optional: true + "@simplewebauthn/server": + optional: true + nodemailer: + optional: true + checksum: 669eea9bf2d84e97d179f2dd0b7f7915269aa23d6014d0dd177f072bf3f894f1a6844a346bcdea868325976cc064baa6c26925715447ba61aa96875782458969 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" @@ -3919,6 +3945,13 @@ __metadata: languageName: node linkType: hard +"@panva/hkdf@npm:^1.2.1": + version: 1.2.1 + resolution: "@panva/hkdf@npm:1.2.1" + checksum: a4a9d1812f88f02bc163b365524bbaa5239cc4711e5e7be1bda68dabae1c896cf1cd12520949b0925a6910733d1afcb25ab51fd3cf06f0f69aee988fffebf56e + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.5.0": version: 2.5.0 resolution: "@parcel/watcher-android-arm64@npm:2.5.0" @@ -5025,6 +5058,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 5edce7995775b0b196b142883e4d4f71fd93c294eaec973670f1fa2540b70ea7390408ed513ddefef5fcb12a578100c76596e8f2a714b0c2ae9f70ee773f4510 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -8561,6 +8601,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: cec5e425549b3650eb5c3498a9ba3cde0b9cd419e3b36e4b92739d30b4d89e0b678b98c1ddc209ce7cf958cd3215671fd6ac47aec21f10c2a0cc68abd399d8a7 + languageName: node + linkType: hard + "cookie@npm:^0.4.1": version: 0.4.2 resolution: "cookie@npm:0.4.2" @@ -13425,6 +13472,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.9.3": + version: 5.9.6 + resolution: "jose@npm:5.9.6" + checksum: 4b536da0201858ed4c4582e8bb479081f11e0c63dd0f5e473adde16fc539785e1f2f0409bc1fc7cbbb5b68026776c960b4952da3a06f6fdfff0b9764c9127ae0 + languageName: node + linkType: hard + "js-sha3@npm:0.8.0, js-sha3@npm:^0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -14957,6 +15011,28 @@ __metadata: languageName: node linkType: hard +"next-auth@npm:beta": + version: 5.0.0-beta.25 + resolution: "next-auth@npm:5.0.0-beta.25" + dependencies: + "@auth/core": 0.37.2 + peerDependencies: + "@simplewebauthn/browser": ^9.0.1 + "@simplewebauthn/server": ^9.0.2 + next: ^14.0.0-0 || ^15.0.0-0 + nodemailer: ^6.6.5 + react: ^18.2.0 || ^19.0.0-0 + peerDependenciesMeta: + "@simplewebauthn/browser": + optional: true + "@simplewebauthn/server": + optional: true + nodemailer: + optional: true + checksum: 5a3f52e6d32ae6c954e438496b96bb8d46ba60ca6dbfab146af718d50e7fe8b967b7019fda162e5fa32bfc5219160f7b995c327d62d5144e7935bb18b9b61e39 + languageName: node + linkType: hard + "next-nprogress-bar@npm:~2.3.13": version: 2.3.15 resolution: "next-nprogress-bar@npm:2.3.15" @@ -15356,6 +15432,13 @@ __metadata: languageName: node linkType: hard +"oauth4webapi@npm:^3.0.0": + version: 3.1.4 + resolution: "oauth4webapi@npm:3.1.4" + checksum: d4b81096020ae3edb4170ee229368848ed0bb49a1bca02f5c39778951246d6519c8afbcbaeaffb2c36862af8493c121a439964f0ca9e4c7da9cafb18cb019474 + languageName: node + linkType: hard + "obj-multiplex@npm:^1.0.0": version: 1.0.0 resolution: "obj-multiplex@npm:1.0.0" @@ -16283,6 +16366,24 @@ __metadata: languageName: node linkType: hard +"preact-render-to-string@npm:5.2.3": + version: 5.2.3 + resolution: "preact-render-to-string@npm:5.2.3" + dependencies: + pretty-format: ^3.8.0 + peerDependencies: + preact: ">=10" + checksum: 6e46288d8956adde35b9fe3a21aecd9dea29751b40f0f155dea62f3896f27cb8614d457b32f48d33909d2da81135afcca6c55077520feacd7d15164d1371fb44 + languageName: node + linkType: hard + +"preact@npm:10.11.3": + version: 10.11.3 + resolution: "preact@npm:10.11.3" + checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367 + languageName: node + linkType: hard + "preact@npm:^10.16.0": version: 10.25.1 resolution: "preact@npm:10.25.1" @@ -16361,6 +16462,13 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^3.8.0": + version: 3.8.0 + resolution: "pretty-format@npm:3.8.0" + checksum: 21a114d43ef06978f8f7f6212be4649b0b094f05d9b30e14e37550bf35c8ca24d8adbca9e5adc4cc15d9eaf7a1e7a30478a4dc37b30982bfdf0292a5b385484c + languageName: node + linkType: hard + "pretty-ms@npm:7.0.1": version: 7.0.1 resolution: "pretty-ms@npm:7.0.1" @@ -17439,6 +17547,7 @@ __metadata: graphql: ^16.9.0 husky: ~8.0.3 lint-staged: ~13.2.2 + next-auth: beta languageName: unknown linkType: soft