Skip to content

Commit

Permalink
Merge pull request #42 from hollow-leaf/feat/donation-page
Browse files Browse the repository at this point in the history
Feat/donation page&Twitch_API_Test
  • Loading branch information
crypto0627 authored Feb 13, 2024
2 parents da2727e + 43a3ead commit 490eec4
Show file tree
Hide file tree
Showing 18 changed files with 598 additions and 92 deletions.
5 changes: 4 additions & 1 deletion apps/web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ dist-ssr
*.sw?

# vscode
.vscode
.vscode

#dotenv
.env
2 changes: 1 addition & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/hoobank.svg" />
Expand Down
4 changes: 4 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.2",
"dotenv": "16.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.0",
"react-scripts": "^5.0.1"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/dotenv": "^8.2.0",
"@types/node": "^16.18.79",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
Expand Down
Binary file added apps/web/src/assets/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 28 additions & 26 deletions apps/web/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import airbnb from "./airbnb.png";
import bill from "./bill.png";
import binance from "./binance.png";
import card from "./card.png";
import coinbase from "./coinbase.png";
import dropbox from "./dropbox.png";
import logo from "./logo.svg";
import quotes from "./quotes.svg";
import robot from "./robot.png";
import send from "./Send.svg";
import shield from "./Shield.svg";
import star from "./Star.svg";
import menu from "./menu.svg";
import close from "./close.svg";
import google from "./google.svg";
import apple from "./apple.svg";
import arrowUp from "./arrow-up.svg";
import discount from "./Discount.svg";
import facebook from "./facebook.svg";
import instagram from "./instagram.svg";
import linkedin from "./linkedin.svg";
import twitter from "./twitter.svg";
import people01 from "./people01.png";
import people02 from "./people02.png";
import people03 from "./people03.png";
import airbnb from "./airbnb.png"
import bill from "./bill.png"
import binance from "./binance.png"
import card from "./card.png"
import coinbase from "./coinbase.png"
import dropbox from "./dropbox.png"
import logo from "./logo.svg"
import quotes from "./quotes.svg"
import robot from "./robot.png"
import send from "./Send.svg"
import shield from "./Shield.svg"
import star from "./Star.svg"
import menu from "./menu.svg"
import close from "./close.svg"
import google from "./google.svg"
import apple from "./apple.svg"
import arrowUp from "./arrow-up.svg"
import discount from "./Discount.svg"
import facebook from "./facebook.svg"
import instagram from "./instagram.svg"
import linkedin from "./linkedin.svg"
import twitter from "./twitter.svg"
import people01 from "./people01.png"
import people02 from "./people02.png"
import people03 from "./people03.png"
import background from './background.png'

export {
airbnb,
Expand Down Expand Up @@ -50,4 +51,5 @@ export {
people01,
people02,
people03,
};
background,
}
18 changes: 18 additions & 0 deletions apps/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
} from 'react-router-dom'
import App from './App'
import Launch from './routes/launch'
import Donation from './routes/donation'
import TwitchAuth from './routes/authcallback'
import ErrorPage from './routes/error'
import Callback from './routes/callback'

const rootElement = document.getElementById('root')

Expand All @@ -21,6 +24,21 @@ const router = createBrowserRouter([
path: 'launch',
element: <Launch />,
errorElement: <ErrorPage />,
},
{
path: 'donation',
element: <Donation />,
errorElement: <ErrorPage />,
},
{
path: 'auth/callback',
element: <Callback />,
errorElement: <ErrorPage />,
},
{
path: 'twitchtest',
element: <TwitchAuth />,
errorElement: <ErrorPage />,
}
])

Expand Down
56 changes: 56 additions & 0 deletions apps/web/src/routes/authcallback.tsx
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;
Binary file added apps/web/src/routes/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions apps/web/src/routes/callback.tsx
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;
14 changes: 14 additions & 0 deletions apps/web/src/routes/components/Navbar.tsx
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>
)
}
96 changes: 96 additions & 0 deletions apps/web/src/routes/donation.tsx
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>
)
}
}
42 changes: 29 additions & 13 deletions apps/web/src/routes/error.tsx
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 <></>;
}
}
Loading

0 comments on commit 490eec4

Please sign in to comment.