Skip to content

Commit

Permalink
Merge pull request #1 from Moonsong-Labs/feat/initial-recovery-views
Browse files Browse the repository at this point in the history
feat: add initial guardian recovery views
  • Loading branch information
aon authored Jan 16, 2025
2 parents b9afdc3 + 880f3d2 commit 213fff5
Show file tree
Hide file tree
Showing 9 changed files with 597 additions and 180 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<template>
<Dialog
ref="modalRef"
content-class="min-w-[700px] min-h-[500px]"
description-class="flex-1 mb-0 flex"
close-class="h-8 max-h-8"
:title="title"
>
<template #trigger>
<slot name="trigger">
<Button
class="w-full lg:w-auto"
type="primary"
>
Add Recovery Method
</Button>
</slot>
</template>

<template #submit>
<div />
</template>

<template #cancel>
<div />
</template>

<!-- Method Selection Step -->
<div
v-if="currentStep === 'select-method'"
class="space-y-4 text-left flex-1 flex flex-col"
>
<p class="text-gray-600 mb-6">
Choose a recovery method for your account:
</p>
<div class="flex flex-col gap-5 items-center flex-1 justify-center">
<Button
class="w-64"
@click="selectMethod('guardian')"
>
<div class="flex items-center justify-between gap-2">
<UserIcon class="w-5 h-5" />
<span>Guardian Recovery</span>
</div>
</Button>
<div class="flex flex-col gap-2">
<Button
disabled
class="w-64"
>
<div class="flex items-center justify-between gap-2">
<EnvelopeIcon class="w-5 h-5" />
<span>Email Recovery</span>
</div>
</Button>
<span class="text-sm text-gray-500 text-center">
Coming soon...
</span>
</div>
</div>
</div>

<GuardianFlow
v-if="currentStep === 'guardian'"
:close-modal="closeModal"
@back="currentStep = 'select-method'"
/>
</Dialog>
</template>

<script setup lang="ts">
import { EnvelopeIcon, UserIcon } from "@heroicons/vue/24/solid";
import { ref } from "vue";
import GuardianFlow from "~/components/account-recovery/flows/GuardianFlow.vue";
import Button from "~/components/zk/button.vue";
import Dialog from "~/components/zk/dialog.vue";
type Step = "select-method" | "guardian" | "email";
const currentStep = ref<Step>("select-method");
const modalRef = ref<InstanceType<typeof Dialog>>();
function closeModal() {
modalRef.value?.close();
}
const title = computed(() => {
switch (currentStep.value) {
case "select-method":
return "Add Recovery Method";
case "guardian":
return "Guardian Recovery Setup";
case "email":
return "Email Recovery Setup";
default:
throw new Error("Invalid step");
}
});
function selectMethod(method: "guardian" | "email") {
currentStep.value = method;
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<template>
<div
v-if="currentStep === 'info'"
class="gap-4 flex-1 flex flex-col justify-center items-center"
>
<p class="text-gray-600 px-4">
Guardian recovery allows you to designate trusted contacts who can help you
recover your account if you lose access.
</p>
<div class="flex space-x-3">
<Button
type="primary"
class="w-fit"
@click="currentStep = 'add-guardian'"
>
Continue
</Button>
<Button
type="secondary"
class="w-fit"
@click="$emit('back')"
>
Back
</Button>
</div>
</div>

<div
v-else-if="currentStep === 'add-guardian'"
class="flex flex-col gap-4 flex-1 text-left justify-center px-6"
>
<p>Insert address</p>
<Input />
<div class="flex gap-3">
<Button @click="currentStep = 'confirm'">
Continue
</Button>
<Button
type="secondary"
@click="currentStep = 'info'"
>
Back
</Button>
</div>
</div>

<div
v-else-if="currentStep === 'confirm'"
class="flex flex-col justify-between flex-1 text-left px-6"
>
<p>Your recovery address was saved. Please use this url to confirm the recovery method:</p>
<Link
href="https://auth-test.zksync.dev/dashboard/0x1234567890"
class="w-fit mx-auto"
target="_blank"
>
https://auth-test.zksync.dev/dashboard/0x1234567890
</Link>
<Button @click="completeSetup">
Close
</Button>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import Button from "~/components/zk/button.vue";
import Input from "~/components/zk/input.vue";
import Link from "~/components/zk/link.vue";
type GuardianStep = "info" | "add-guardian" | "confirm";
const currentStep = ref<GuardianStep>("info");
const props = defineProps<{
closeModal: () => void;
}>();
defineEmits<{
(e: "back"): void;
}>();
function completeSetup() {
props.closeModal();
}
</script>
69 changes: 69 additions & 0 deletions packages/auth-server/components/common/CopyToClipboard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<button
class="relative w-5 h-5 flex items-center justify-center"
@click="copyToClipboard(text)"
>
<Transition name="fade">
<span
v-if="copied"
class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white dark:text-gray-900 bg-gray-800 dark:bg-gray-100 rounded-2xl whitespace-nowrap"
>
Copied!
</span>
</Transition>
<Transition
name="scale"
mode="out-in"
>
<CheckIcon
v-if="copied"
class="w-5 h-5 text-green-500 dark:text-green-400"
/>
<DocumentDuplicateIcon
v-else
class="w-4 h-4"
/>
</Transition>
</button>
</template>

<script setup lang="ts">
import { CheckIcon, DocumentDuplicateIcon } from "@heroicons/vue/24/solid";
import { ref } from "vue";
const copied = ref(false);
const copyToClipboard = async (text: string) => {
await navigator.clipboard.writeText(text);
copied.value = true;
setTimeout(() => {
copied.value = false;
}, 2000);
};
defineProps<{ text: string }>();
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.scale-enter-active,
.scale-leave-active {
transition: all 0.1s ease-out;
}
.scale-enter-from,
.scale-leave-to {
transform: scale(0.95);
opacity: 0;
}
</style>
48 changes: 36 additions & 12 deletions packages/auth-server/components/zk/dialog.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
<template>
<Dialog.Root>
<Dialog.Trigger>
<ZkButton>Dialog</ZkButton>
<Dialog.Root
:open="open"
>
<Dialog.Trigger
:as-child="true"
@click="open = true"
>
<slot name="trigger">
<ZkButton>Dialog</ZkButton>
</slot>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay
class="data-[state=open]:animate-overlayShow fixed inset-0 z-30 bg-neutral-900/70"
@click="open = false"
/>
<Dialog.Content
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[350px] translate-x-[-50%] translate-y-[-50%] rounded-zk bg-white focus:outline-none z-[100] p-4 dark:bg-neutral-950 border border-transparent dark:border-neutral-900 dark:text-neutral-100"
:class="['data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[350px] translate-x-[-50%] translate-y-[-50%] flex flex-col rounded-zk bg-white focus:outline-none z-[100] p-6 dark:bg-neutral-950 border border-transparent dark:border-neutral-900 dark:text-neutral-100', contentClass]"
>
<Dialog.Title class="text-lg flex items-center mb-8">
<span class="flex-auto">{{ title }}</span>
<Dialog.Close
class="inline-flex appearance-none items-center justify-center focus:outline-none focus:ring-1 rounded-full"
aria-label="Close"
@click="open = false"
>
<ZkIcon icon="close" />
</Dialog.Close>
</Dialog.Title>
<Dialog.Description class="mb-10 text-lg text-center">
<Dialog.Description :class="['mb-10 text-lg text-center', descriptionClass]">
<slot />
</Dialog.Description>

<div class="flex flex-col gap-2 justify-end">
<div :class="['flex flex-col gap-2 justify-end flex-1', closeClass]">
<Dialog.Close as-child>
<slot name="submit">
<ZkButton
Expand All @@ -35,12 +44,14 @@
</slot>
</Dialog.Close>
<Dialog.Close as-child>
<ZkButton
type="secondary"
class="w-full"
>
Cancel
</ZkButton>
<slot name="cancel">
<ZkButton
type="secondary"
class="w-full"
>
Cancel
</ZkButton>
</slot>
</Dialog.Close>
</div>
</Dialog.Content>
Expand All @@ -51,7 +62,20 @@
<script setup lang="ts">
import { Dialog } from "radix-vue/namespaced";
const open = ref(false);
const closeModal = () => {
open.value = false;
};
defineProps<{
title: string;
contentClass?: string;
descriptionClass?: string;
closeClass?: string;
}>();
defineExpose({
close: closeModal,
});
</script>
2 changes: 1 addition & 1 deletion packages/auth-server/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default defineNuxtConfig({
public: {
chainId: parseInt(process.env.NUXT_PUBLIC_DEFAULT_CHAIN_ID || "") || zksyncInMemoryNode.id,
[zksyncInMemoryNode.id]: {
nftQuestAddress: "",
nftQuestAddress: "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021",
},
[zksyncSepoliaTestnet.id]: {
nftQuestAddress: "0x4D533d3B20b50b57268f189F93bFaf8B39c36AB6",
Expand Down
3 changes: 3 additions & 0 deletions packages/auth-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"web3-avatar-vue": "^1.0.4",
"zksync-sso": "workspace:*"
},
"devDependencies": {
"tailwindcss": "^3.4.14"
},
"overrides": {
"vue": "latest"
}
Expand Down
Loading

0 comments on commit 213fff5

Please sign in to comment.