Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat github middleware #41

Merged
merged 9 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,041 changes: 1,751 additions & 1,290 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ onMounted(() => {
window.addEventListener('load', () => {
gsap.to(DOMSiteloader.value, { autoAlpha: 0, duration: 0.4 })
store.titleToggle()

// Handle GitHub OAuth return if needed
store.handleGithubReturn()
})
})
</script>
Expand Down
180 changes: 23 additions & 157 deletions src/components/faucet/FaucetDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
ref="DOMpopup"
class="popup fixed flex flex-col items-center rounded w-[90vw] max-w-[36rem] max-h-[90vh] justify-start bg-grey-300 top-[45%] left-1/2 -translate-x-1/2 -translate-y-1/2 z-[1000] md:justify-center text-grey-50 before:absolute before:top-0 before:bottom-0 before:left-0 before:right-0 before:rounded before:bg-500 before:z-min after:absolute after:top-px after:left-px after:bottom-px after:right-px after:bg-grey-500 after:rounded after:z-min"
>
<div ref="DOMFaucetRequest" class="py-8 md:py-12 px-8 md:px-20 w-full overflow-scroll no-scrollbar" v-if="store.isVisible">
<FaucetContentForm :name="store.selectedFaucet.name ?? 'Faucet'" :options="store.faucetAmount" v-show="store.contentStep === 0" class="js-faucetform opacity-100" :error="error" @requestFaucet="requestFaucet" />
<div ref="DOMFaucetRequest" class="py-8 md:py-12 px-8 md:px-20 w-full overflow-scroll no-scrollbar" v-show="store.isVisible">
<FaucetContentForm :name="store.selectedFaucet.name ?? 'Faucet'" :options="store.faucetAmount" v-show="store.contentStep === 0" class="js-faucetform opacity-100" :error="store.error" />
<div>
<div ref="gnoRequestLogo" v-show="store.contentStep >= 1" class="opacity-0">
<Vue3Lottie :animationData="GnoJSON" :loop="true" :height="200" :width="200" :autoPlay="true" />
</div>
<FaucetContentRequest v-show="store.contentStep === 1" class="js-faucetpending" />
<FaucetContentSuccess v-show="store.contentStep === 2" :tx-link="txLink" class="js-faucetsuccess opacity-0" @doneFaucet="donefaucet()" />
<FaucetContentSuccess v-show="store.contentStep === 2" class="js-faucetsuccess opacity-0" @doneFaucet="donefaucet()" />
</div>
</div>
</section>
</template>

<script setup lang="ts">
import { onMounted, ref, reactive, watch, nextTick } from 'vue'
import { onMounted, onUpdated, ref } from 'vue'
import { gsap } from 'gsap'
import { Vue3Lottie } from 'vue3-lottie'

Expand All @@ -30,174 +30,40 @@ import GnoJSON from '@/assets/lottie/logo.json'

import { useFaucetDetail } from '@/stores/faucetDetail'

const txLink = ref('')

const store = useFaucetDetail()

const DOMbackground = ref<HTMLElement | null>(null)
const DOMpopup = ref<HTMLElement | null>(null)
const DOMFaucetRequest = ref<HTMLElement | null>(null)
const gnoRequestLogo = ref<HTMLElement | null>(null)

const popupHeight = reactive({ from: 0, to: 0 })
const error = ref<string | null>(null)

const closePopup = () => store.popupToggle()

const requestFaucet = async (address: string, amount: number, secret: string) => {
popupHeight.from = DOMpopup.value?.getBoundingClientRect().height ?? 0
gsap.set(DOMpopup.value, { height: popupHeight.from + 'px' })

gsap.to('.js-faucetform', {
autoAlpha: 0,
duration: 0.5,
onComplete: () => {
store.status = 'pending'
store.contentStep = 1
},
})
gsap.to(gnoRequestLogo.value, { autoAlpha: 1, delay: 0.5 })

// min default loading timer
const minTimer = new Promise((resolve) => setTimeout(resolve, 2000))

const displayError = (e: string) => {
error.value = e
store.status = 'error'

console.error(e)
}
try {
const response = await fetch(store.selectedFaucet.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: address,
amount: amount * 1000000 + 'ugnot', //TODO: need to be dynamyc if different token
captcha: secret,
}),
})

await minTimer
const faucetResponse = await response.json()

store.status = response.status !== 200 || faucetResponse.error ? 'error' : 'success'

// Check the faucet response
if (response.status === 200 && store.status === 'success') {
store.status = 'success'
txLink.value = faucetResponse.result ?? '' //TODO: get tx link
} else {
displayError(faucetResponse.error)
}
} catch (e) {
await minTimer
displayError(e as string)
}
}

const donefaucet = () => store.popupToggle()

const TLPending = ref<GSAPTimeline | null>(null)

onMounted(() => {
// Store DOM references for animations
store.DOM.bg = DOMbackground.value
store.DOM.popup = DOMpopup.value
store.DOM.gnoRequestLogo = gnoRequestLogo.value
store.DOM.faucetRequest = DOMFaucetRequest.value

TLPending.value = gsap
.timeline({ repeat: -1, paused: true })
.fromTo('.popup', { '--mx': '0%' }, { '--mx': '100%', duration: 0.8, ease: 'none' })
.fromTo('.popup', { '--my': '100%' }, { '--my': '0%', duration: 0.8, ease: 'none' })
.fromTo('.popup', { '--mx': '100%' }, { '--mx': '0%', duration: 0.8, ease: 'none' })
.to('.popup', { '--my': '100%', duration: 0.8, ease: 'none' })
})
// Setup the pending animation timeline
store.setupPendingAnimation()

const toggleLoader = (state: boolean) => {
gsap.to('.popup', {
'--op': state ? 1 : 0,
onComplete: () => {
!state && TLPending.value && TLPending.value.pause()
},
// Subscribe to status changes
store.$subscribe((mutation, state) => {
if (mutation.type === 'direct' && mutation.events?.key === 'status') {
store.handleStatusChange(state.status)
}
})
state && TLPending.value && TLPending.value.play()
}

const setPopupHeight = () => {
popupHeight.to = DOMFaucetRequest.value?.getBoundingClientRect().height ?? 0
gsap.to(DOMpopup.value, { height: popupHeight.to + 'px', duration: 0.4 })
}

watch(
() => store.status,
(value) => {
nextTick(() => {
switch (value) {
case 'pending':
toggleLoader(true)
setPopupHeight()
break

case 'error':
toggleLoader(false)
setPopupHeight()

gsap.to('.js-faucetpending', {
autoAlpha: 0,
duration: 0.6,
onComplete: () => {
store.contentStep = 0
popupHeight.from = popupHeight.to
gsap.set(DOMpopup.value, { height: 'auto' })

gsap.to('.js-faucetform', {
autoAlpha: 1,
duration: 0.5,
})
gsap.set('.js-faucetpending', { autoAlpha: 1 })
},
})
break

case 'success':
toggleLoader(false)

gsap.to('.js-faucetpending', {
autoAlpha: 0,
duration: 0.6,
onComplete: () => {
store.contentStep = 2
popupHeight.from = popupHeight.to
gsap.set(DOMpopup.value, { height: 'auto' })

gsap.to('.js-faucetsuccess', {
autoAlpha: 1,
duration: 0.6,
})

//TODO: replace into own component "FaucetContentSuccess"
gsap.to('.js-faucetsuccessdetail', {
height: 'auto',
duration: 0.6,
})
},
})
break
})

case 'null':
gsap.set('.js-faucetform', { autoAlpha: 1 })
gsap.set('.js-faucetpending', { autoAlpha: 1 })
gsap.set('.js-faucetsuccess', { autoAlpha: 0 })
gsap.set('.js-faucetsuccessdetail', { height: 0 })
gsap.set(DOMpopup.value, { height: 'auto', delay: 0.4 })
onUpdated(() => {
store.DOM.bg = DOMbackground.value
store.DOM.popup = DOMpopup.value
store.DOM.gnoRequestLogo = gnoRequestLogo.value
store.DOM.faucetRequest = DOMFaucetRequest.value
})

error.value = null
break
}
})
},
)
const closePopup = () => store.popupToggle()
const donefaucet = () => store.popupToggle()
</script>

<style scoped>
Expand Down
33 changes: 25 additions & 8 deletions src/components/faucet/content/FaucetContentForm.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<template>
<div>
<h2 class="text-500 md:text-600 mb-8 md:mb-12">{{ name }}</h2>
<form class="w-full space-y-7 md:space-y-12" @submit.prevent="isAddressValid && captchaValid && requestFaucet()">
<form class="w-full space-y-7 md:space-y-12" @submit.prevent="handleSubmit">
<Input :label="'Enter your wallet address'" :placeholder="'e.g. g1juwee0ynsdvaukvxk3j5s4cl6nn24uxwlydxrl'" v-model="bindAddress" required />
<Select v-if="store.selectedFaucet.amounts" :label="'Select faucet amount'" :options="options" @update="(option) => SelectAmount(option)" />
<Recaptcha :key="store.status" @validation="captchaValidation" :captchakey="store.selectedFaucet.recaptcha" />
<Recaptcha v-if="!store.selectedFaucet.github_oauth_client_id" :key="store.status" @validation="captchaValidation" :captchakey="store.selectedFaucet.recaptcha" />
<div>
<div class="flex flex-col md:flex-row gap-4">
<Button text="Cancel" variant="outline" @click.prevent="() => closePopup()" class="w-full" />
<Button text="Request drip" class="w-full" type="submit" :disabled="captchaValid === false || !isAddressValid" />
<Button text="Cancel" variant="outline" @click.prevent="() => closePopup()" class="md:w-1/3" />
<Button :text="buttonText" class="w-full" type="submit" :disabled="!isFormValid" />
</div>
<div v-if="error" class="text-center text-red-200 mt-6">{{ error }}</div>
</div>
Expand Down Expand Up @@ -48,16 +48,33 @@ const captchaValidation = ({ code = 'error', secret = '' }) => {
}

const isAddressValid = computed(() => new RegExp(/^[a-z0-9]{40}$/).test(bindAddress.value))
const isFormValid = computed(() => {
if (store.selectedFaucet.github_oauth_client_id) {
return isAddressValid.value
}
return isAddressValid.value && captchaValid.value
})

const buttonText = computed(() => (store.selectedFaucet.github_oauth_client_id ? 'Connect GitHub & Get Drip' : 'Request drip'))

const SelectAmount = (option: SelectOption) => {
amount.value = option
}

const closePopup = () => store.popupToggle()

const emit = defineEmits(['requestFaucet'])
const handleSubmit = async () => {
if (!isFormValid.value) return

const requestData = {
address: bindAddress.value,
amount: Number(amount.value?.value || 0),
}

const requestFaucet = () => {
if (captchaValid.value === false || bindAddress.value === '') return
emit('requestFaucet', bindAddress.value, store.selectedFaucet.amounts && amount.value?.value, captchaSecret.value)
if (store.selectedFaucet.github_oauth_client_id) {
await store.requestWithGithub(requestData.address, requestData.amount)
} else {
await store.requestWithCaptcha(requestData.address, requestData.amount, captchaSecret.value)
}
}
</script>
6 changes: 1 addition & 5 deletions src/components/faucet/content/FaucetContentSuccess.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,5 @@
<script setup lang="ts">
import Button from '@/components/ui/Button.vue'

interface Props {
txLink?: string // TODO: add link
}

defineProps<Props>()
defineEmits(['doneFaucet'])
</script>
9 changes: 9 additions & 0 deletions src/data/faucets.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
"url": "https://faucet.gno.berty.io",
"description": "The dSocial faucet is here to provide you with a smooth experience when onboarding a new user to the dSocial app. The dApp uses the faucet behind the scenes to provide initial funds for the user to interact with the gno.land dSocial realm.",
"recaptcha": "6LcQz9IpAAAAAIRM9gJkdtANwRM0gaQVBPJq0Mr9"
},
{
"name": "Teritori Faucet",
"chain_id": "test5",
"amounts": [1, 5, 10],
"url": "http://localhost:5050",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure: will this entry be removed or changed to a public hostname before merging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello :)
thanks for the review yes it is just for testing, I think we can leave the comment to change it before merge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed here thanks for your review: 👍
2c849c3

"description": "A development testnet faucet used for Gno Teritori",
"recaptcha": "",
"github_oauth_client_id": "Ov23limbJ7InJel7HiFH"
}
]
}
Loading