-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from hollow-leaf/feat/donation-page
Feat/donation page&Twitch_API_Test
- Loading branch information
Showing
18 changed files
with
598 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,4 +24,7 @@ dist-ssr | |
*.sw? | ||
|
||
# vscode | ||
.vscode | ||
.vscode | ||
|
||
#dotenv | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
|
||
const TwitchAuth: React.FC = () => { | ||
const [twitchData, setTwitchData] = useState<any>({}); | ||
const clientId: string = process.env.CLIENT_ID | ||
const clientSecret: string = process.env.CLIENT_SECRET | ||
const redirectUri: string = process.env.API_REDIRECT_URI | ||
useEffect(() => { | ||
const queryString = window.location.search; | ||
const urlParams = new URLSearchParams(queryString); | ||
const code = urlParams.get('access_token'); | ||
console.log(code) | ||
if (code) { | ||
fetch(`https://id.twitch.tv/oauth2/token?client_id=${clientId}&client_secret=${clientSecret}&code=${code}&grant_type=authorization_code&redirect_uri=${redirectUri}`, { | ||
method: 'POST' | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
console.log(data); | ||
// Now you have access token, use it to fetch user data | ||
fetch('https://api.twitch.tv/helix/users', { | ||
headers: { | ||
'Client-ID': clientId, | ||
'Authorization': `Bearer ${data.access_token}` | ||
} | ||
}) | ||
.then(response => response.json()) | ||
.then(userData => { | ||
console.log(userData); | ||
setTwitchData(userData); | ||
}) | ||
.catch(error => console.error('Error fetching Twitch user data:', error)); | ||
}) | ||
.catch(error => console.error('Error fetching Twitch access token:', error)); | ||
} | ||
}, []); | ||
|
||
const handleLogin = () => { | ||
window.location.href = `https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=${clientId}&redirect_uri=${redirectUri}&scope=channel%3Amanage%3Apolls+channel%3Aread%3Apolls&state=c3ab8aa609ea11e793ae92361f002671`; | ||
}; | ||
|
||
return ( | ||
<div> | ||
{twitchData.display_name ? ( | ||
<div> | ||
<h1>Welcome, {twitchData.display_name}</h1> | ||
<img src={twitchData.profile_image_url} alt="Twitch Profile" /> | ||
</div> | ||
) : ( | ||
<button onClick={handleLogin}>Login with Twitch</button> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default TwitchAuth; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, { useEffect } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; // 如果你使用React Router | ||
|
||
const TwitchCallbackPage: React.FC = () => { | ||
const navigate = useNavigate(); | ||
const queryString = window.location.hash.substring(1); | ||
const urlParams = new URLSearchParams(queryString); | ||
const code = urlParams.get('access_token'); | ||
console.log(code) | ||
useEffect(() => { | ||
// 從URL中獲取授權碼 | ||
if (code) { | ||
// 向Twitch發送請求以獲取存取權杖 | ||
fetch(`https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=74hb97lvv3rf6dnryttjdlq4omz44t&redirect_uri=http://localhost:5173/auth/callback&scope=channel%3Amanage%3Apolls+channel%3Aread%3Apolls&state=c3ab8aa609ea11e793ae92361f002671`, { | ||
method: 'GET' | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
console.log(data); | ||
// 處理存取權杖,例如將其存儲在狀態中或本地存儲中 | ||
// 這裡可能需要一些後續處理 | ||
// 導向回TwitchAuth頁面或其他頁面 | ||
navigate('/twitchtest'); | ||
}) | ||
.catch(error => console.error('Error fetching Twitch access token:', error)); | ||
}else{ | ||
console.log('can not get code') | ||
} | ||
}, [code]); | ||
|
||
return ( | ||
<div> | ||
<p>處理中...</p> | ||
</div> | ||
); | ||
}; | ||
|
||
export default TwitchCallbackPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import React from 'react' | ||
|
||
export default function Navbar() { | ||
return ( | ||
<div className="navbar font-bold"> | ||
<div className="navbar-start"> | ||
<a className="text-xl text-white" href='/'>Psyduck</a> | ||
</div> | ||
<div className="navbar-end"> | ||
<a className="btn">Connect Wallet</a> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import React, { useState, useEffect, useLayoutEffect } from "react" | ||
import Error from "./error" | ||
import Navbar from "./components/Navbar" | ||
import { TwitchContextService } from "../../src/services/api/twitchContext" | ||
import { useLocation } from "react-router-dom" | ||
|
||
export default function Donation() { | ||
const location = useLocation() | ||
const searchParams = new URLSearchParams(location.search) | ||
const streamerId = searchParams.get("value") | ||
|
||
const [tipAmount, setTipAmount] = useState<string>("4.2") | ||
const [otherClick, setOtherClick] = useState<boolean>(false) | ||
const [correctOtherInput, setCorrectOtherInput] = useState<boolean>(true) | ||
const handleOtherAmount = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const value = e.target.value | ||
if (/^[0-9.0-9]*$/.test(value) && value.length <= 10) { | ||
setTipAmount(value) | ||
setCorrectOtherInput(true) | ||
} else { | ||
setCorrectOtherInput(false) | ||
} | ||
} | ||
useEffect(() => { | ||
setOtherClick(false) | ||
}, [tipAmount]) | ||
if (!streamerId) { | ||
return <Error /> | ||
} else { | ||
return ( | ||
<div className="md:max-w-[5120px] w-full bg-cover bg-no-repeat bg-fixed bg-launch min-h-screen grid place-items-start relative"> | ||
<div className="absolute top-0 left-0 w-full h-full bg-black opacity-50"></div> | ||
<div className="mx-auto md:max-w-[650px] w-full md:grid grid-row-5 rounded-xl relative z-10 gap-3"> | ||
<Navbar /> | ||
<div className="bg-slate-200 rounded-xl p-4 h-[200px] bg-cover bg-no-repeat bg-launch-profile shadow-xl relative"> | ||
<div className="avatar absolute pl-4 bottom-0 left-0 gap-4 flex items-center"> | ||
<div className="w-24 rounded-full"> | ||
<img src="https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg" /> | ||
</div> | ||
<div className="bg-white rounded-xl h-8 w-28 text-start pt-1 pl-2"> | ||
<p className="text-black font-bold">{streamerId}</p> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="row-span-3 md:grid grid-cols-10 gap-4 rounded-sm"> | ||
<form className="bg-white col-span-3 md:col-span-5 h-[450px] rounded-xl p-5 grid-row-7"> | ||
<fieldset className="grid grid-row-3 gap-4"> | ||
<label className="font-bold flex">Select amount to tip</label> | ||
<fieldset className="grid grid-cols-3 gap-2 pt-4"> | ||
<button className={`${tipAmount === '3.33' ? "bg-sky-400 rounded-xl text-center font-bold text-slate-100" : "bg-slate-100 rounded-xl text-center font-bold text-sky-400"}`} type='button' onClick={() => setTipAmount('3.33')}>$3.33</button> | ||
<button className={`${tipAmount === '4.2' ? "bg-sky-400 rounded-xl text-center font-bold text-slate-100" : "bg-slate-100 rounded-xl text-center font-bold text-sky-400"}`} type='button' onClick={() => setTipAmount('4.2')}>$4.20</button> | ||
<button className={`${tipAmount === '6.9' ? "bg-sky-400 rounded-xl text-center font-bold text-slate-100" : "bg-slate-100 rounded-xl text-center font-bold text-sky-400"}`} type='button' onClick={() => setTipAmount('6.9')}>$6.90</button> | ||
<button className={`${tipAmount === '13.37' ? "bg-sky-400 rounded-xl text-center font-bold text-slate-100" : "bg-slate-100 rounded-xl text-center font-bold text-sky-400"}`} type='button' onClick={() => setTipAmount('13.37')}>$13.37</button> | ||
<button className={`${otherClick === true ? "bg-sky-400 rounded-xl text-center font-bold text-slate-100" : "bg-slate-100 rounded-xl text-center font-bold text-sky-400"}`} type="button" onClick={() => { setOtherClick(true) }}>OTHER</button> | ||
</fieldset> | ||
<div className="relative"> | ||
<input type="text" placeholder="Input amount" className="input input-bordered input-info w-full max-w-xs" onChange={handleOtherAmount} /> | ||
<div className={`${correctOtherInput === true ? "hidden" : "flex items-center"}`}> | ||
<svg className="h-6 w-6 mr-2 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path> | ||
</svg> | ||
<p className="text-red-500 font-bold">Warning: Only allow number and float in text box!</p> | ||
</div> | ||
</div> | ||
</fieldset> | ||
<fieldset> | ||
<label className="font-bold flex pt-4">Send a message with your tip</label> | ||
<div className="pt-4"> | ||
<input type="text" placeholder={`Message for ${streamerId}`} className="input input-bordered input-info w-full max-w-xs" /> | ||
</div> | ||
<div className="flex items-center pt-4"> | ||
<p className="mr-2">You are logged-in as</p> | ||
<p className="font-bold">xxx</p> | ||
</div> | ||
</fieldset> | ||
</form> | ||
<form className="bg-white col-span-2 md:col-span-5 h-[450px] rounded-xl p-5"> | ||
<fieldset className="grid grid-rows-5"> | ||
<div className="flex items-center"> | ||
<label className="font-bold flex text-gray-400">Your tip</label> | ||
<label className="font-bold pl-40">${tipAmount}</label> | ||
</div> | ||
<div className="divider"></div> | ||
<label className="font-bold text-black">Tip {streamerId}</label> | ||
<button className="btn btn-wide rounded-3xl">Psyduck</button> | ||
<div className="row-span-4"> | ||
<label className="text-gray-400 absolute align-middle bottom-0 text-center pr-2">By moving forward you confirm you’ve read and approved our Terms of Service.</label> | ||
</div> | ||
</fieldset> | ||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,33 @@ | ||
import React from 'react' | ||
import { useRouteError } from 'react-router-dom' | ||
import { useRouteError } from "react-router-dom"; | ||
|
||
type ErrorResponse = { | ||
data: any; | ||
status: number; | ||
statusText: string; | ||
message?: string; | ||
}; | ||
|
||
const errorCheck = (error: any): error is ErrorResponse => { | ||
return "data" in error && "status" in error && "statusText" in error; | ||
}; | ||
|
||
export default function ErrorPage() { | ||
const error: unknown = useRouteError() | ||
console.error(error) | ||
const error: any = useRouteError(); | ||
console.log(error); | ||
// console.log(typeof error); | ||
|
||
return ( | ||
<div className='mx-auto font-bold bg-slate-400'> | ||
<h1>Oops!</h1> | ||
<p>Sorry, an unexpected error has occurred.</p> | ||
<p> | ||
<i>{error.statusText || error.message}</i> | ||
</p> | ||
</div> | ||
) | ||
} | ||
if (errorCheck(error)) { | ||
return ( | ||
<div id="error-page"> | ||
<h1>Oops! Page not found</h1> | ||
<p>Sorry the route you are looking for does not exist.</p> | ||
<p> | ||
<i>{error.statusText || error.message}</i> | ||
</p> | ||
</div> | ||
); | ||
} else { | ||
return <></>; | ||
} | ||
} |
Oops, something went wrong.