CompleteProfileModal.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <template>
  2. <Teleport to="body">
  3. <Transition
  4. enter-active-class="transition duration-200"
  5. enter-from-class="opacity-0"
  6. enter-to-class="opacity-100"
  7. leave-active-class="transition duration-150"
  8. leave-from-class="opacity-100"
  9. leave-to-class="opacity-0"
  10. >
  11. <div v-if="isOpen" class="fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6">
  12. <!-- Backdrop -->
  13. <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" />
  14. <!-- Modal -->
  15. <div
  16. v-motion
  17. :initial="{ opacity: 0, scale: 0.9, y: 20 }"
  18. :enter="{ opacity: 1, scale: 1, y: 0 }"
  19. class="relative w-full max-w-lg bg-card/90 border border-border/50 rounded-3xl shadow-2xl overflow-hidden backdrop-blur-xl"
  20. >
  21. <div class="absolute top-0 inset-x-0 h-1 bg-gradient-to-r from-primary/50 via-primary to-primary/50" />
  22. <div class="p-8">
  23. <div class="text-center mb-8">
  24. <div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 text-primary mb-4">
  25. <MapPin class="w-8 h-8" />
  26. </div>
  27. <h2 class="text-2xl font-bold font-display mb-2">
  28. {{ t("profile.complete_title") || "Complete Your Profile" }}
  29. </h2>
  30. <p class="text-muted-foreground text-sm">
  31. {{ t("profile.complete_subtitle") || "We need a few more details to process your 3D printing orders." }}
  32. </p>
  33. </div>
  34. <form @submit.prevent="handleSubmit" class="space-y-6">
  35. <div class="space-y-4">
  36. <!-- Phone -->
  37. <div class="space-y-2">
  38. <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">
  39. {{ t("upload.phone") }}
  40. </label>
  41. <div class="relative group">
  42. <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
  43. <Phone class="w-4 h-4" />
  44. </div>
  45. <input
  46. v-model="formData.phone"
  47. type="tel"
  48. required
  49. placeholder="+381 60 123 4567"
  50. class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-sm"
  51. />
  52. </div>
  53. </div>
  54. <!-- Shipping Address -->
  55. <div class="space-y-2">
  56. <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">
  57. {{ t("upload.shippingAddress") }}
  58. </label>
  59. <div class="relative group">
  60. <div class="absolute top-3 left-4 text-muted-foreground group-focus-within:text-primary transition-colors">
  61. <MapPin class="w-4 h-4" />
  62. </div>
  63. <textarea
  64. v-model="formData.shipping_address"
  65. required
  66. rows="3"
  67. placeholder="Street name, Number, City, Postal Code"
  68. class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-sm resize-none"
  69. />
  70. </div>
  71. </div>
  72. </div>
  73. <Button type="submit" variant="hero" class="w-full" :disabled="isLoading">
  74. <Loader2 v-if="isLoading" class="w-5 h-5 animate-spin" />
  75. <template v-else>
  76. {{ t("common.save_continue") || "Save & Continue" }}
  77. <ArrowRight class="w-4 h-4 ml-2" />
  78. </template>
  79. </Button>
  80. </form>
  81. </div>
  82. </div>
  83. </div>
  84. </Transition>
  85. </Teleport>
  86. </template>
  87. <script setup lang="ts">
  88. import { ref, reactive } from "vue";
  89. import { useI18n } from "vue-i18n";
  90. import { toast } from "vue-sonner";
  91. import { Phone, MapPin, Loader2, ArrowRight } from "lucide-vue-next";
  92. import Button from "./ui/button.vue";
  93. import { updateProfile } from "@/lib/api";
  94. const props = defineProps<{ isOpen: boolean; user: any }>();
  95. const emit = defineEmits<{ complete: [] }>();
  96. const { t } = useI18n();
  97. const isLoading = ref(false);
  98. const formData = reactive({
  99. phone: props.user?.phone ?? "",
  100. shipping_address: props.user?.shipping_address ?? "",
  101. });
  102. async function handleSubmit() {
  103. if (!formData.phone || !formData.shipping_address) {
  104. toast.error(t("errors.missing_fields") || "Please fill all fields");
  105. return;
  106. }
  107. isLoading.value = true;
  108. try {
  109. await updateProfile(formData);
  110. toast.success(t("profile.updated_success") || "Profile completed successfully!");
  111. emit("complete");
  112. } catch (err: any) {
  113. toast.error(err.message);
  114. } finally {
  115. isLoading.value = false;
  116. }
  117. }
  118. </script>