diff --git a/Cargo.lock b/Cargo.lock index 7a1695e0a..6ba272dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,9 +875,12 @@ dependencies = [ [[package]] name = "ic-stable-structures" -version = "0.6.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4867a1d9f232e99ca68682161d1fc67dff9501f4f1bf42d69a9358289ad0f8" +checksum = "07e2282054c8ddf0cb2a7abf5c174c373917b4345c9a096ae4aa7f7185cdcdc7" +dependencies = [ + "ic_principal", +] [[package]] name = "ic0" diff --git a/Cargo.toml b/Cargo.toml index 0c7ed822a..169a19a5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ ic-cdk = "0.13.1" ic-cdk-macros = "0.9" ic-cdk-timers = "0.7.0" ic-ledger-types = "0.10.0" -ic-stable-structures = "0.6.0" +ic-stable-structures = "0.6.4" lazy_static = "1.4.0" mockall = "0.12.1" num-bigint = "0.4" diff --git a/apps/wallet/src/components/accounts/TransferDialog.vue b/apps/wallet/src/components/accounts/TransferDialog.vue index 1a07c5fb5..6e10aea99 100644 --- a/apps/wallet/src/components/accounts/TransferDialog.vue +++ b/apps/wallet/src/components/accounts/TransferDialog.vue @@ -35,7 +35,7 @@ { const mod = (await importOriginal()) as object; @@ -22,6 +23,18 @@ vi.mock('~/stores/station.store', async importOriginal => { }; }); +vi.mock('~/services/station.service', () => { + const mock: Partial = { + withStationId: vi.fn().mockReturnThis(), + capabilities: vi.fn().mockImplementation(() => Promise.resolve({})), + isHealthy: vi.fn().mockResolvedValue(true), + }; + + return { + StationService: vi.fn(() => mock), + }; +}); + describe('DeployStation', () => { it('renders correctly', () => { vi.spyOn(services().controlPanel, 'canDeployStation').mockResolvedValueOnce( @@ -95,7 +108,7 @@ describe('DeployStation', () => { expect(wrapper.find('[data-test-id="deploy-quota-exceeded-error"]').exists()).toBe(true); }); - it('will show the deploy screen if the is approved to create a new wallet', async () => { + it('will show the deploy screen if the user is approved to create a new wallet', async () => { vi.spyOn(services().controlPanel, 'canDeployStation').mockResolvedValue({ Allowed: BigInt(10), } as CanDeployStationResponse); @@ -111,6 +124,18 @@ describe('DeployStation', () => { await flushPromises(); + const form = wrapper.find('[data-test-id="deploy-station-form"]'); + + form.find('input[name="station_name"]').setValue('test'); + form.find('input[name="admin_name"]').setValue('admin'); + + await flushPromises(); + + form.trigger('submit'); + + await wrapper.vm.$nextTick(); + await flushPromises(); + expect(wrapper.find('[data-test-id="deploying-station"]').exists()).toBe(true); // will redirect after deploy is complete diff --git a/apps/wallet/src/components/add-station/DeployStation.vue b/apps/wallet/src/components/add-station/DeployStation.vue index b9f5f3969..8ee871ed7 100644 --- a/apps/wallet/src/components/add-station/DeployStation.vue +++ b/apps/wallet/src/components/add-station/DeployStation.vue @@ -31,7 +31,7 @@ type="email" :rules="[requiredRule, validEmail]" :label="$t('pages.add_station.join_waitlist_email_field')" - :variant="'outlined'" + variant="filled" hide-details="auto" :disabled="working" data-test-id="join-waitlist-form-email" @@ -49,6 +49,57 @@ + +

+ {{ $t('pages.add_station.station_title') }} +

+

+ {{ $t('pages.add_station.station_body') }} +

+ + + + + +
+ + {{ $t('terms.create') }} + +
+
+
(CanDeployStatus.CheckPermissions); @@ -169,6 +222,11 @@ const working = ref(false); const form = ref(null); const isFormValid = computed(() => (form.value ? form.value.isValid : false)); +const stationName = ref(''); +const adminName = ref(''); +const stationForm = ref(null); +const isStationFormValid = computed(() => (stationForm.value ? stationForm.value.isValid : false)); + const waitUntilStationIsInitialized = async ( stationId: Principal, { retries, retryWaitMs }: { retries?: number; retryWaitMs?: number } = {}, @@ -201,7 +259,10 @@ const waitUntilStationIsInitialized = async ( const deployInitialStation = async (): Promise => { try { deploymentStatus.value = DeployStationStatus.Deploying; - const stationId = await controlPanelService.deployStation(); + const stationId = await controlPanelService.deployStation({ + station_name: stationName.value, + admin_name: adminName.value, + }); const controlPanelUser = await controlPanelService.getCurrentUser(); // wait for the station to be initialized, this requires one round of consensus @@ -242,14 +303,25 @@ async function joinWaitlist() { } } +async function stationFormSubmit() { + deploymentStatus.value = DeployStationStatus.Starting; + canDeployStatus.value = CanDeployStatus.Approved; + + try { + await deployInitialStation(); + } catch (e: unknown) { + app.sendErrorNotification(e); + deploymentStatus.value = DeployStationStatus.Failed; + } +} + onMounted(async () => { try { const canDeploy = await controlPanelService.canDeployStation(); if (variantIs(canDeploy, 'NotAllowed')) { if (variantIs(canDeploy.NotAllowed, 'Approved')) { - deploymentStatus.value = DeployStationStatus.Starting; - canDeployStatus.value = CanDeployStatus.Approved; + canDeployStatus.value = CanDeployStatus.PickName; await deployInitialStation(); } else if (variantIs(canDeploy.NotAllowed, 'Denylisted')) { canDeployStatus.value = CanDeployStatus.NotAllowed; @@ -265,9 +337,7 @@ onMounted(async () => { } if (variantIs(canDeploy, 'Allowed')) { - deploymentStatus.value = DeployStationStatus.Starting; - canDeployStatus.value = CanDeployStatus.Approved; - await deployInitialStation(); + canDeployStatus.value = CanDeployStatus.PickName; return; } diff --git a/apps/wallet/src/components/add-station/JoinStation.spec.ts b/apps/wallet/src/components/add-station/JoinStation.spec.ts index a52291374..e5510ed9e 100644 --- a/apps/wallet/src/components/add-station/JoinStation.spec.ts +++ b/apps/wallet/src/components/add-station/JoinStation.spec.ts @@ -4,6 +4,22 @@ import JoinStation from './JoinStation.vue'; import { useSessionStore } from '~/stores/session.store'; import { flushPromises } from '@vue/test-utils'; import { useAppStore } from '~/stores/app.store'; +import { StationService } from '~/services/station.service'; + +vi.mock('~/services/station.service', () => { + const mock: Partial = { + withStationId: vi.fn().mockReturnThis(), + capabilities: vi.fn().mockImplementation(() => + Promise.resolve({ + name: 'test', + }), + ), + }; + + return { + StationService: vi.fn(() => mock), + }; +}); describe('JoinStation', () => { it('renders correctly', () => { @@ -45,16 +61,16 @@ describe('JoinStation', () => { const canisterId = wrapper.find('[data-test-id="join-station-form-canister-id"] input'); - // name is optional - expect( - wrapper.find('[data-test-id="join-station-form-canister-name"]').classes(), - ).not.toContain('v-input--error'); - // canister id is required expect(wrapper.find('[data-test-id="join-station-form-canister-id"]').classes()).toContain( 'v-input--error', ); + // name is required + expect(wrapper.find('[data-test-id="join-station-form-canister-name"]').classes()).toContain( + 'v-input--error', + ); + await canisterId.setValue('hello'); await flushPromises(); diff --git a/apps/wallet/src/components/add-station/JoinStation.vue b/apps/wallet/src/components/add-station/JoinStation.vue index d0c4822bc..6dde3701a 100644 --- a/apps/wallet/src/components/add-station/JoinStation.vue +++ b/apps/wallet/src/components/add-station/JoinStation.vue @@ -34,6 +34,7 @@ v-model.trim="name" :label="$t('pages.add_station.join_station_name')" data-test-id="join-station-form-canister-name" + :rules="[requiredRule, maxLengthRule(40, $t('pages.add_station.join_station_name'))]" variant="outlined" :disabled="working" /> @@ -59,11 +60,16 @@ import { VFormValidation } from '~/types/helper.types'; import { useSessionStore } from '~/stores/session.store'; import { ref } from 'vue'; import { computed } from 'vue'; -import { requiredRule, validCanisterId } from '~/utils/form.utils'; +import { maxLengthRule, requiredRule, validCanisterId } from '~/utils/form.utils'; import { useRouter } from 'vue-router'; import { defaultHomeRoute } from '~/configs/routes.config'; import { useAppStore } from '~/stores/app.store'; import { copyToClipboard } from '~/utils/app.utils'; +import { StationService } from '~/services/station.service'; +import { icAgent } from '~/core/ic-agent.core'; +import { Principal } from '@dfinity/principal'; +import logger from '~/core/logger.core'; +import { watch } from 'vue'; const session = useSessionStore(); const router = useRouter(); @@ -75,11 +81,40 @@ const name = ref(''); const form = ref(null); const isFormValid = computed(() => (form.value ? form.value.isValid : false)); +const stationService = new StationService(icAgent.get()); const emit = defineEmits<{ (event: 'back', payload: void): void; }>(); +const maybeParseCanisterId = (canisterId: string): Principal | undefined => { + try { + return Principal.fromText(canisterId); + } catch { + return undefined; + } +}; + +const onChangeCanisterIdMaybeFetchName = async () => { + let stationId = maybeParseCanisterId(canisterId.value); + if (!stationId) { + return; + } + + try { + const station = await stationService.withStationId(stationId).capabilities(); + + name.value = station.name; + } catch (e: unknown) { + logger.warn('Failed to fetch station name', e); + } +}; + +watch( + () => canisterId.value, + () => onChangeCanisterIdMaybeFetchName(), +); + async function addNewStation() { if (working.value) { return; diff --git a/apps/wallet/src/components/inputs/UserAutocomplete.vue b/apps/wallet/src/components/inputs/UserAutocomplete.vue index 372075074..8eca27836 100644 --- a/apps/wallet/src/components/inputs/UserAutocomplete.vue +++ b/apps/wallet/src/components/inputs/UserAutocomplete.vue @@ -91,7 +91,7 @@ watch( results => { const updatedItems = results.map(result => ({ value: result.id, - text: result.name?.[0] ?? result.id, + text: result.name, })); updateAvailableItemsList(updatedItems); diff --git a/apps/wallet/src/components/permissions/IndividualUserPermissions.vue b/apps/wallet/src/components/permissions/IndividualUserPermissions.vue index a5375c7a5..b4ec613a2 100644 --- a/apps/wallet/src/components/permissions/IndividualUserPermissions.vue +++ b/apps/wallet/src/components/permissions/IndividualUserPermissions.vue @@ -75,7 +75,7 @@ const { fetchPolicies } = toRefs(props); const userList = computed(() => { const users = autocomplete.results.value.map(user => ({ - title: user.name?.[0] ? user.name[0] : user.id, + title: user.name, value: user.id, })); diff --git a/apps/wallet/src/components/permissions/SpecificUsersForm.vue b/apps/wallet/src/components/permissions/SpecificUsersForm.vue index ce7f6411f..73fe7e2cb 100644 --- a/apps/wallet/src/components/permissions/SpecificUsersForm.vue +++ b/apps/wallet/src/components/permissions/SpecificUsersForm.vue @@ -64,14 +64,14 @@ const model = computed({ const userList = computed(() => { const users = usersAutocomplete.results.value.map(user => ({ - title: user.name?.[0] ? user.name[0] : user.id, + title: user.name, value: user.id, })); (model.value.prefilledUsers ?? []).forEach(user => { if (!users.find(g => g.value === user.id)) { users.push({ - title: user.name?.[0] ? user.name[0] : user.id, + title: user.name, value: user.id, }); } diff --git a/apps/wallet/src/components/requests/RequestDetailView.spec.ts b/apps/wallet/src/components/requests/RequestDetailView.spec.ts index e3ab0f19b..9387e4bac 100644 --- a/apps/wallet/src/components/requests/RequestDetailView.spec.ts +++ b/apps/wallet/src/components/requests/RequestDetailView.spec.ts @@ -7,8 +7,8 @@ type RequestDetailViewProps = InstanceType['$props']; const pendingProps: RequestDetailViewProps = { details: { can_approve: true, - requester_name: undefined, - approvers: [{ id: 'requester-id', name: [] }], + requester_name: 'requester', + approvers: [{ id: 'requester-id', name: '' }], }, request: { status: { Created: null }, @@ -26,7 +26,7 @@ const pendingProps: RequestDetailViewProps = { input: { groups: [], identities: [], - name: ['test'], + name: 'test', status: { Active: null }, }, }, @@ -44,10 +44,10 @@ const pendingProps: RequestDetailViewProps = { const approvedProps: RequestDetailViewProps = { details: { can_approve: false, - requester_name: undefined, + requester_name: 'requester', approvers: [ - { id: 'approver-1-id', name: [] }, - { id: 'approver-2-id', name: [] }, + { id: 'approver-1-id', name: '' }, + { id: 'approver-2-id', name: '' }, ], }, request: { @@ -72,7 +72,7 @@ const approvedProps: RequestDetailViewProps = { input: { groups: [], identities: [], - name: ['test'], + name: 'test', status: { Active: null }, }, }, @@ -90,10 +90,10 @@ const approvedProps: RequestDetailViewProps = { const rejectedProps: RequestDetailViewProps = { details: { can_approve: false, - requester_name: undefined, + requester_name: 'requester', approvers: [ - { id: 'approver-1-id', name: [] }, - { id: 'approver-2-id', name: [] }, + { id: 'approver-1-id', name: '' }, + { id: 'approver-2-id', name: '' }, ], }, request: { @@ -118,7 +118,7 @@ const rejectedProps: RequestDetailViewProps = { input: { groups: [], identities: [], - name: ['test'], + name: 'test', status: { Active: null }, }, }, diff --git a/apps/wallet/src/components/requests/RequestDetailView.vue b/apps/wallet/src/components/requests/RequestDetailView.vue index db004b82c..06053d92f 100644 --- a/apps/wallet/src/components/requests/RequestDetailView.vue +++ b/apps/wallet/src/components/requests/RequestDetailView.vue @@ -74,7 +74,7 @@ - {{ approval.approver.name?.[0] || approval.approver.id }} + {{ approval.approver.name || approval.approver.id }} return { approver: approver || { id: approval.approver_id, - name: [], + name: '', }, approval, }; diff --git a/apps/wallet/src/components/requests/RequestDialog.spec.ts b/apps/wallet/src/components/requests/RequestDialog.spec.ts index 617ab6200..eb8b93429 100644 --- a/apps/wallet/src/components/requests/RequestDialog.spec.ts +++ b/apps/wallet/src/components/requests/RequestDialog.spec.ts @@ -48,7 +48,7 @@ const approvableRequestResponse = { }, additional_info: { id: 'requesterid', - requester_name: [], + requester_name: '', }, //... } as ExtractOk; @@ -63,7 +63,7 @@ const nextApprovableRequestResponse = { }, additional_info: { id: 'next-id', - requester_name: [], + requester_name: '', }, //... } as GetRequestResultData; @@ -78,7 +78,7 @@ const completedRequestResponse = { }, additional_info: { id: 'first-id', - requester_name: [], + requester_name: '', }, //... } as ExtractOk; diff --git a/apps/wallet/src/components/requests/RequestDialog.vue b/apps/wallet/src/components/requests/RequestDialog.vue index 7b5d22a7d..a0d5f2a4b 100644 --- a/apps/wallet/src/components/requests/RequestDialog.vue +++ b/apps/wallet/src/components/requests/RequestDialog.vue @@ -28,7 +28,7 @@ :request="data.request" :details="{ can_approve: data.privileges.can_approve, - requester_name: data.additionalInfo.requester_name[0], + requester_name: data.additionalInfo.requester_name, approvers: data.additionalInfo.approvers, }" :loading="approving || loading" diff --git a/apps/wallet/src/components/requests/RequestList.vue b/apps/wallet/src/components/requests/RequestList.vue index 8c96f17e0..d8d25f7f8 100644 --- a/apps/wallet/src/components/requests/RequestList.vue +++ b/apps/wallet/src/components/requests/RequestList.vue @@ -72,7 +72,7 @@ const getDetails = (request: Request): RequestDetails => { return { can_approve: !!privileges?.can_approve, - requester_name: info?.requester_name?.[0] ?? '', + requester_name: info?.requester_name ?? '', approvers: info?.approvers ?? [], }; }; diff --git a/apps/wallet/src/components/requests/RequestMetadata.spec.ts b/apps/wallet/src/components/requests/RequestMetadata.spec.ts index 2d41f29e9..6a0f0b63c 100644 --- a/apps/wallet/src/components/requests/RequestMetadata.spec.ts +++ b/apps/wallet/src/components/requests/RequestMetadata.spec.ts @@ -7,7 +7,7 @@ describe('RequestMetadata', () => { it('renders properly', () => { const wrapper = mount(RequestMetadata, { props: { - details: { can_approve: false, requester_name: undefined, approvers: [] }, + details: { can_approve: false, requester_name: 'requester', approvers: [] }, request: { status: { Approved: null }, } as Request, @@ -20,7 +20,7 @@ describe('RequestMetadata', () => { it('shows the expiration_dt when still pending', () => { const wrapper = mount(RequestMetadata, { props: { - details: { can_approve: false, requester_name: undefined, approvers: [] }, + details: { can_approve: false, requester_name: 'requester', approvers: [] }, request: { expiration_dt: new Date().toISOString(), status: { Created: null }, @@ -35,7 +35,7 @@ describe('RequestMetadata', () => { it('hides the expiration_dt when not pending', () => { const wrapper = mount(RequestMetadata, { props: { - details: { can_approve: false, requester_name: undefined, approvers: [] }, + details: { can_approve: false, requester_name: 'requester', approvers: [] }, request: { expiration_dt: new Date().toISOString(), status: { Approved: null }, diff --git a/apps/wallet/src/components/requests/RequestMetadata.vue b/apps/wallet/src/components/requests/RequestMetadata.vue index 9e8464dd6..39d8d5de0 100644 --- a/apps/wallet/src/components/requests/RequestMetadata.vue +++ b/apps/wallet/src/components/requests/RequestMetadata.vue @@ -10,16 +10,12 @@ data-test-id="requested_by" > - {{ - props.details.requester_name - ? props.details.requester_name - : props.request.requested_by - }} + {{ props.details.requester_name }} - {{ $t('requests.requested_by', { name: props.details.requester_name ?? '-' }) }} + {{ $t('requests.requested_by', { name: props.details.requester_name }) }}
{{ $t('requests.requester_id', { id: props.request.requested_by }) }}
diff --git a/apps/wallet/src/components/requests/operations/AddUserOperation.vue b/apps/wallet/src/components/requests/operations/AddUserOperation.vue index ab769fb54..b6718c2b3 100644 --- a/apps/wallet/src/components/requests/operations/AddUserOperation.vue +++ b/apps/wallet/src/components/requests/operations/AddUserOperation.vue @@ -3,7 +3,7 @@ diff --git a/apps/wallet/src/components/requests/operations/EditUserOperation.vue b/apps/wallet/src/components/requests/operations/EditUserOperation.vue index 20b7afd4c..2a106f379 100644 --- a/apps/wallet/src/components/requests/operations/EditUserOperation.vue +++ b/apps/wallet/src/components/requests/operations/EditUserOperation.vue @@ -9,7 +9,7 @@ @@ -46,7 +46,7 @@ const formValue: Ref> = ref({}); onBeforeMount(() => { const user: Partial = {}; user.id = props.operation.input.id; - user.name = props.operation.input.name; + user.name = props.operation.input.name?.[0]; if (props.operation.input.status?.[0]) { user.status = props.operation.input.status?.[0]; } diff --git a/apps/wallet/src/components/settings/StationInfoCard.spec.ts b/apps/wallet/src/components/settings/StationInfoCard.spec.ts index 84719f064..207647189 100644 --- a/apps/wallet/src/components/settings/StationInfoCard.spec.ts +++ b/apps/wallet/src/components/settings/StationInfoCard.spec.ts @@ -18,7 +18,7 @@ vi.mock('~/services/control-panel.service', () => { }); describe('StationInfoCard', () => { - function initStation(principal: Principal, isMain: boolean, name: string | null) { + function initStation(principal: Principal, isMain: boolean, name: string) { const stationStore = useStationStore(); const sessionStore = useSessionStore(); @@ -31,7 +31,7 @@ describe('StationInfoCard', () => { stationStore.$patch({ canisterId: principal.toText() }); } - function addStation(principal: Principal, isMain: boolean, name: string | null) { + function addStation(principal: Principal, isMain: boolean, name: string) { const sessionStore = useSessionStore(); const stationStore = useStationStore(); @@ -128,7 +128,7 @@ describe('StationInfoCard', () => { [ { canister_id: stationCanisterId1, - name: ['TEST WALLET'], + name: 'TEST WALLET', }, ], ], diff --git a/apps/wallet/src/components/settings/StationInfoCard.vue b/apps/wallet/src/components/settings/StationInfoCard.vue index 090013d2d..b28969b1c 100644 --- a/apps/wallet/src/components/settings/StationInfoCard.vue +++ b/apps/wallet/src/components/settings/StationInfoCard.vue @@ -182,13 +182,13 @@ const save = async ({ model }: { valid: boolean; model: StationInfoModel }): Pro session.data.stations.map(entry => { if (entry.canisterId === station.canisterId) { return { - name: model.name ? [model.name] : [], + name: model.name, canister_id: Principal.fromText(entry.canisterId), }; } return { - name: entry.name ? [entry.name] : [], + name: entry.name, canister_id: Principal.fromText(entry.canisterId), }; }) ?? []; diff --git a/apps/wallet/src/components/users/UserDialog.vue b/apps/wallet/src/components/users/UserDialog.vue index 470fd45f4..087ab7c20 100644 --- a/apps/wallet/src/components/users/UserDialog.vue +++ b/apps/wallet/src/components/users/UserDialog.vue @@ -141,7 +141,7 @@ const save = async (): Promise => { id: user.value.id, groups: [assertAndReturn(user.value.groups, 'groups').map(g => g.id)], identities: [assertAndReturn(user.value.identities, 'identities')], - name: user.value.name !== undefined ? user.value.name : [], + name: [assertAndReturn(user.value.name, 'name')], status: [assertAndReturn(user.value.status, 'status')], }); @@ -154,7 +154,7 @@ const save = async (): Promise => { const request = await station.service.addUser({ groups: assertAndReturn(user.value.groups, 'groups').map(g => g.id), identities: assertAndReturn(user.value.identities, 'identities'), - name: user.value.name !== undefined ? user.value.name : [], + name: assertAndReturn(user.value.name, 'name'), status: assertAndReturn(user.value.status, 'status'), }); diff --git a/apps/wallet/src/components/users/UserForm.spec.ts b/apps/wallet/src/components/users/UserForm.spec.ts index 48564b6eb..627d2b343 100644 --- a/apps/wallet/src/components/users/UserForm.spec.ts +++ b/apps/wallet/src/components/users/UserForm.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; import { mount } from '~/test.utils'; import UserForm from './UserForm.vue'; -describe('AddPrincipalForm', () => { +describe('UserForm', () => { it('renders properly', () => { const wrapper = mount(UserForm, { props: { @@ -30,7 +30,7 @@ describe('AddPrincipalForm', () => { await nameInput.setValue('Test'); expect(wrapper.emitted('update:modelValue')).toBeTruthy(); - expect(wrapper.emitted('update:modelValue')).toEqual([[{ name: ['Test'] }]]); + expect(wrapper.emitted('update:modelValue')).toEqual([[{ name: 'Test' }]]); }); it('submits the form and emits it', async () => { @@ -58,7 +58,7 @@ describe('AddPrincipalForm', () => { expect(wrapper.emitted('submit')).toEqual([ [ { - name: ['Test'], + name: 'Test', groups: [{ id: '1', name: 'test' }], identities: [Principal.anonymous()], status: { Active: null }, diff --git a/apps/wallet/src/components/users/UserForm.vue b/apps/wallet/src/components/users/UserForm.vue index c49523984..d124af2f3 100644 --- a/apps/wallet/src/components/users/UserForm.vue +++ b/apps/wallet/src/components/users/UserForm.vue @@ -14,7 +14,7 @@ name="name" :label="$t('terms.name')" density="comfortable" - :rules="[maxLengthRule(100, $t('terms.name'))]" + :rules="[maxLengthRule(50, $t('terms.name'))]" :variant="isViewMode ? 'plain' : 'filled'" :disabled="isViewMode" /> @@ -170,9 +170,9 @@ const status = computed({ }); const name = computed({ - get: () => model.value.name?.[0], + get: () => model.value.name, set: value => { - model.value.name = !value ? [] : [value]; + model.value.name = !value ? '' : value; }, }); diff --git a/apps/wallet/src/generated/control-panel/control_panel.did b/apps/wallet/src/generated/control-panel/control_panel.did index d561bebfc..147111ade 100644 --- a/apps/wallet/src/generated/control-panel/control_panel.did +++ b/apps/wallet/src/generated/control-panel/control_panel.did @@ -110,7 +110,7 @@ type UserStation = record { // The id associated with the station. canister_id : StationID; // The name of the station. - name : opt text; + name : text; }; // The result of listing stations. @@ -149,7 +149,7 @@ type GetUserResult = variant { // The input for registering an user. type RegisterUserInput = record { // A station canister to use for this user. - station_id : opt principal; + station : opt UserStation; }; // The result of registering an user. @@ -195,6 +195,14 @@ type CanDeployStationResponse = variant { QuotaExceeded; }; +/// The input for deploying a station canister. +type DeployStationInput = record { + // The station name. + station_name : text; + // The station admin user name. + admin_name : text; +}; + // The result of checking if the caller can deploy a station canister. type CanDeployStationResult = variant { // Successfull operation result. @@ -264,7 +272,7 @@ service : (opt CanisterInstall) -> { // Get main station for the caller user. get_main_station : () -> (GetMainStationResult) query; // Deploys a new station canister for the caller. - deploy_station : () -> (DeployStationResult); + deploy_station : (input : DeployStationInput) -> (DeployStationResult); // Checks if the caller can deploy a new station canister. can_deploy_station : () -> (CanDeployStationResult) query; // HTTP Protocol interface. diff --git a/apps/wallet/src/generated/control-panel/control_panel.did.d.ts b/apps/wallet/src/generated/control-panel/control_panel.did.d.ts index 9698ac522..15731bf6d 100644 --- a/apps/wallet/src/generated/control-panel/control_panel.did.d.ts +++ b/apps/wallet/src/generated/control-panel/control_panel.did.d.ts @@ -24,6 +24,10 @@ export interface CanisterUpgrade { 'station_wasm_module' : [] | [Uint8Array | number[]], 'upgrader_wasm_module' : [] | [Uint8Array | number[]], } +export interface DeployStationInput { + 'admin_name' : string, + 'station_name' : string, +} export type DeployStationResult = { 'Ok' : { 'canister_id' : StationID } } | { 'Err' : ApiError }; export type GetMainStationResult = { @@ -59,7 +63,7 @@ export interface ManageUserInput { } export type ManageUserResult = { 'Ok' : { 'user' : User } } | { 'Err' : ApiError }; -export interface RegisterUserInput { 'station_id' : [] | [Principal] } +export interface RegisterUserInput { 'station' : [] | [UserStation] } export type RegisterUserResult = { 'Ok' : { 'user' : User } } | { 'Err' : ApiError }; export type RemoveUserResult = { 'Ok' : { 'user' : User } } | @@ -90,10 +94,7 @@ export interface User { } export type UserId = UUID; export type UserIdentityID = Principal; -export interface UserStation { - 'name' : [] | [string], - 'canister_id' : StationID, -} +export interface UserStation { 'name' : string, 'canister_id' : StationID } export type UserSubscriptionStatus = { 'Unsubscribed' : null } | { 'Approved' : null } | { 'Denylisted' : null } | @@ -101,7 +102,7 @@ export type UserSubscriptionStatus = { 'Unsubscribed' : null } | export interface _SERVICE { 'can_deploy_station' : ActorMethod<[], CanDeployStationResult>, 'delete_user' : ActorMethod<[], RemoveUserResult>, - 'deploy_station' : ActorMethod<[], DeployStationResult>, + 'deploy_station' : ActorMethod<[DeployStationInput], DeployStationResult>, 'get_main_station' : ActorMethod<[], GetMainStationResult>, 'get_user' : ActorMethod<[], GetUserResult>, 'get_waiting_list' : ActorMethod<[], GetWaitingListResult>, diff --git a/apps/wallet/src/generated/control-panel/control_panel.did.js b/apps/wallet/src/generated/control-panel/control_panel.did.js index 8672793d7..5a79b7f63 100644 --- a/apps/wallet/src/generated/control-panel/control_panel.did.js +++ b/apps/wallet/src/generated/control-panel/control_panel.did.js @@ -33,7 +33,7 @@ export const idlFactory = ({ IDL }) => { }); const StationID = IDL.Principal; const UserStation = IDL.Record({ - 'name' : IDL.Opt(IDL.Text), + 'name' : IDL.Text, 'canister_id' : StationID, }); const TimestampRFC3339 = IDL.Text; @@ -48,6 +48,10 @@ export const idlFactory = ({ IDL }) => { 'Ok' : IDL.Record({ 'user' : User }), 'Err' : ApiError, }); + const DeployStationInput = IDL.Record({ + 'admin_name' : IDL.Text, + 'station_name' : IDL.Text, + }); const DeployStationResult = IDL.Variant({ 'Ok' : IDL.Record({ 'canister_id' : StationID }), 'Err' : ApiError, @@ -95,9 +99,7 @@ export const idlFactory = ({ IDL }) => { 'Ok' : IDL.Record({ 'user' : User }), 'Err' : ApiError, }); - const RegisterUserInput = IDL.Record({ - 'station_id' : IDL.Opt(IDL.Principal), - }); + const RegisterUserInput = IDL.Record({ 'station' : IDL.Opt(UserStation) }); const RegisterUserResult = IDL.Variant({ 'Ok' : IDL.Record({ 'user' : User }), 'Err' : ApiError, @@ -121,7 +123,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ 'can_deploy_station' : IDL.Func([], [CanDeployStationResult], ['query']), 'delete_user' : IDL.Func([], [RemoveUserResult], []), - 'deploy_station' : IDL.Func([], [DeployStationResult], []), + 'deploy_station' : IDL.Func( + [DeployStationInput], + [DeployStationResult], + [], + ), 'get_main_station' : IDL.Func([], [GetMainStationResult], ['query']), 'get_user' : IDL.Func([], [GetUserResult], ['query']), 'get_waiting_list' : IDL.Func([], [GetWaitingListResult], []), diff --git a/apps/wallet/src/generated/station/station.did b/apps/wallet/src/generated/station/station.did index 1ecbb4e1a..9bb39d51b 100644 --- a/apps/wallet/src/generated/station/station.did +++ b/apps/wallet/src/generated/station/station.did @@ -427,7 +427,7 @@ type RemoveAddressBookEntryOperationInput = record { type AddUserOperationInput = record { // The user name (e.g. "John Doe"). - name : opt text; + name : text; // The principals associated with the user. identities : vec principal; // The list of groups the user belongs to. @@ -711,7 +711,7 @@ type RequestAdditionalInfo = record { // The request id. id : UUID; // The requester name (e.g. "John Doe"). - requester_name : opt text; + requester_name : text; // Display information for the approvers. approvers : vec DisplayUser; // The evaluation result of all matching policies for the request. @@ -1037,7 +1037,7 @@ type User = record { // The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). id : UUID; // The user name (e.g. "John Doe"). - name : opt text; + name : text; // The status of the user (e.g. `Active`). status : UserStatus; // The list of groups the user belongs to. @@ -1368,9 +1368,11 @@ type Asset = record { metadata : vec AssetMetadata; }; -// A record type that is used to show the current capabilities of the canister and it's version. +// A record type that is used to show the current capabilities of the station. type Capabilities = record { - // Version of the canister. + // The name of the station. + name : text; + // Version of the station. version : text; // The list of supported assets. supported_assets : vec Asset; @@ -1387,9 +1389,11 @@ type CapabilitiesResult = variant { Err : Error; }; -// The canister system information. +// The system information. type SystemInfo = record { - // The canister version. + // The name of the station. + name : text; + // The station version. version : text; // The upgrader principal id. upgrader_id : principal; @@ -1591,7 +1595,7 @@ type DisplayUser = record { // The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). id : UUID; // The user name (e.g. "John Doe"). - name : opt text; + name : text; }; // Result type for listing permissions. @@ -1739,20 +1743,31 @@ type MeResult = variant { Err : Error; }; +// The admin that is created in the station during the init process. +type AdminInitInput = record { + // The name of the user. + name : text; + // The identity of the admin. + identity : principal; +}; + // The init configuration for the canister. // // Only used when installing the canister for the first time. type SystemInit = record { + // The name of the station. + name : text; // The list of admin principals to be associated with the station. - // - // When not specified, the caller will be added as the admin. - admins : opt vec principal; + admins : vec AdminInitInput; // The wasm module of the station upgrader canister. upgrader_wasm_module : blob; }; // The upgrade configuration for the canister. -type SystemUpgrade = record {}; +type SystemUpgrade = record { + // The updated name of the station. + name : opt text; +}; // The input type for the canister install method (e.g. init or upgrade). type SystemInstall = variant { diff --git a/apps/wallet/src/generated/station/station.did.d.ts b/apps/wallet/src/generated/station/station.did.d.ts index 7b60c6872..d495b8f03 100644 --- a/apps/wallet/src/generated/station/station.did.d.ts +++ b/apps/wallet/src/generated/station/station.did.d.ts @@ -84,7 +84,7 @@ export interface AddUserOperation { export interface AddUserOperationInput { 'status' : UserStatus, 'groups' : Array, - 'name' : [] | [string], + 'name' : string, 'identities' : Array, } export interface AddressBookEntry { @@ -102,6 +102,7 @@ export interface AddressBookEntryCallerPrivileges { 'can_edit' : boolean, } export interface AddressBookMetadata { 'key' : string, 'value' : string } +export interface AdminInitInput { 'name' : string, 'identity' : Principal } export interface Allow { 'user_groups' : Array, 'auth_scope' : AuthScope, @@ -125,6 +126,7 @@ export interface BasicUser { 'name' : string, } export interface Capabilities { + 'name' : string, 'version' : string, 'supported_assets' : Array, } @@ -163,7 +165,7 @@ export type CreateRequestResult = { } } | { 'Err' : Error }; -export interface DisplayUser { 'id' : UUID, 'name' : [] | [string] } +export interface DisplayUser { 'id' : UUID, 'name' : string } export interface EditAccountOperation { 'input' : EditAccountOperationInput } export interface EditAccountOperationInput { 'account_id' : UUID, @@ -544,7 +546,7 @@ export interface Request { export interface RequestAdditionalInfo { 'id' : UUID, 'evaluation_result' : [] | [RequestEvaluationResult], - 'requester_name' : [] | [string], + 'requester_name' : string, 'approvers' : Array, } export interface RequestApproval { @@ -711,6 +713,7 @@ export type SubmitRequestApprovalResult = { } | { 'Err' : Error }; export interface SystemInfo { + 'name' : string, 'last_upgrade_timestamp' : TimestampRFC3339, 'raw_rand_successful' : boolean, 'version' : string, @@ -720,14 +723,15 @@ export interface SystemInfo { export type SystemInfoResult = { 'Ok' : { 'system' : SystemInfo } } | { 'Err' : Error }; export interface SystemInit { - 'admins' : [] | [Array], + 'name' : string, + 'admins' : Array, 'upgrader_wasm_module' : Uint8Array | number[], } export type SystemInstall = { 'Upgrade' : SystemUpgrade } | { 'Init' : SystemInit }; export type SystemResourceAction = { 'SystemInfo' : null } | { 'Capabilities' : null }; -export type SystemUpgrade = {}; +export interface SystemUpgrade { 'name' : [] | [string] } export type TimestampRFC3339 = string; export interface Transfer { 'id' : UUID, @@ -782,7 +786,7 @@ export interface User { 'id' : UUID, 'status' : UserStatus, 'groups' : Array, - 'name' : [] | [string], + 'name' : string, 'last_modification_timestamp' : TimestampRFC3339, 'identities' : Array, } diff --git a/apps/wallet/src/generated/station/station.did.js b/apps/wallet/src/generated/station/station.did.js index 849e50cdc..d74c9b700 100644 --- a/apps/wallet/src/generated/station/station.did.js +++ b/apps/wallet/src/generated/station/station.did.js @@ -1,9 +1,14 @@ export const idlFactory = ({ IDL }) => { const RequestPolicyRule = IDL.Rec(); const RequestPolicyRuleResult = IDL.Rec(); - const SystemUpgrade = IDL.Record({}); + const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) }); + const AdminInitInput = IDL.Record({ + 'name' : IDL.Text, + 'identity' : IDL.Principal, + }); const SystemInit = IDL.Record({ - 'admins' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'name' : IDL.Text, + 'admins' : IDL.Vec(AdminInitInput), 'upgrader_wasm_module' : IDL.Vec(IDL.Nat8), }); const SystemInstall = IDL.Variant({ @@ -20,6 +25,7 @@ export const idlFactory = ({ IDL }) => { 'symbol' : AssetSymbol, }); const Capabilities = IDL.Record({ + 'name' : IDL.Text, 'version' : IDL.Text, 'supported_assets' : IDL.Vec(Asset), }); @@ -102,7 +108,7 @@ export const idlFactory = ({ IDL }) => { const AddUserOperationInput = IDL.Record({ 'status' : UserStatus, 'groups' : IDL.Vec(UUID), - 'name' : IDL.Opt(IDL.Text), + 'name' : IDL.Text, 'identities' : IDL.Vec(IDL.Principal), }); const EditUserGroupOperationInput = IDL.Record({ @@ -302,7 +308,7 @@ export const idlFactory = ({ IDL }) => { 'id' : UUID, 'status' : UserStatus, 'groups' : IDL.Vec(UserGroup), - 'name' : IDL.Opt(IDL.Text), + 'name' : IDL.Text, 'last_modification_timestamp' : TimestampRFC3339, 'identities' : IDL.Vec(IDL.Principal), }); @@ -458,11 +464,11 @@ export const idlFactory = ({ IDL }) => { 'status' : EvaluationStatus, 'policy_results' : IDL.Vec(RequestPolicyRuleResult), }); - const DisplayUser = IDL.Record({ 'id' : UUID, 'name' : IDL.Opt(IDL.Text) }); + const DisplayUser = IDL.Record({ 'id' : UUID, 'name' : IDL.Text }); const RequestAdditionalInfo = IDL.Record({ 'id' : UUID, 'evaluation_result' : IDL.Opt(RequestEvaluationResult), - 'requester_name' : IDL.Opt(IDL.Text), + 'requester_name' : IDL.Text, 'approvers' : IDL.Vec(DisplayUser), }); const CreateRequestResult = IDL.Variant({ @@ -897,6 +903,7 @@ export const idlFactory = ({ IDL }) => { 'Err' : Error, }); const SystemInfo = IDL.Record({ + 'name' : IDL.Text, 'last_upgrade_timestamp' : TimestampRFC3339, 'raw_rand_successful' : IDL.Bool, 'version' : IDL.Text, @@ -1010,9 +1017,14 @@ export const idlFactory = ({ IDL }) => { }); }; export const init = ({ IDL }) => { - const SystemUpgrade = IDL.Record({}); + const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) }); + const AdminInitInput = IDL.Record({ + 'name' : IDL.Text, + 'identity' : IDL.Principal, + }); const SystemInit = IDL.Record({ - 'admins' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'name' : IDL.Text, + 'admins' : IDL.Vec(AdminInitInput), 'upgrader_wasm_module' : IDL.Vec(IDL.Nat8), }); const SystemInstall = IDL.Variant({ diff --git a/apps/wallet/src/locales/en.locale.ts b/apps/wallet/src/locales/en.locale.ts index 8b20a688b..aa48d0d8b 100644 --- a/apps/wallet/src/locales/en.locale.ts +++ b/apps/wallet/src/locales/en.locale.ts @@ -546,9 +546,15 @@ export default { join_station_body: 'Contact the owner to get the Wallet ID and send them your identity so that a user can be created for you.', join_station_canister_id: 'Wallet ID', - join_station_name: 'Wallet Name (optional)', + join_station_name: 'Wallet Name', join_station: 'Join wallet', + station_title: 'Create your own wallet', + station_body: + 'Create your own wallet and manage your digital assets. You can add users, set permissions and manage request approval policies.', + station_name_field: 'Wallet Name', + admin_name_field: 'Admin Name', + check_permissions_title: 'Checking waiting list status...', join_waitlist_title: 'Join waiting list', join_waitlist_body: diff --git a/apps/wallet/src/locales/fr.locale.ts b/apps/wallet/src/locales/fr.locale.ts index fc6436262..bf1d2d669 100644 --- a/apps/wallet/src/locales/fr.locale.ts +++ b/apps/wallet/src/locales/fr.locale.ts @@ -555,6 +555,12 @@ export default { join_waitlist_email_field: 'Entrez votre adresse e-mail', join_waitlist: "S'inscrire maintenant", + station_title: 'Créer un portefeuille', + station_body: + 'Créez un portefeuille pour gérer vos actifs numériques. Entrez un nom pour votre portefeuille et cliquez sur "Créer".', + station_name_field: 'Nom du Portefeuille', + admin_name_field: 'Nom de l Administrateur', + waitlist_pending_title: 'Vous êtes sur la liste d attente!', waitlist_pending_body: 'Veuillez attendre l approbation. Vous recevrez un email une fois votre demande approuvée.', @@ -573,7 +579,7 @@ export default { join_station_body: "Contactez le propriétaire pour obtenir l'ID du portefeuille et envoyez-lui votre identité afin qu'un utilisateur puisse être créé pour vous.", join_station_canister_id: 'ID du Portefeuille', - join_station_name: 'Nom du Portefeuille (optionnel)', + join_station_name: 'Nom du Portefeuille', join_station: 'Rejoindre le portefeuille', status_starting: 'Initialization, veuillez patienter...', diff --git a/apps/wallet/src/locales/pt.locale.ts b/apps/wallet/src/locales/pt.locale.ts index 7f2455105..023cf4159 100644 --- a/apps/wallet/src/locales/pt.locale.ts +++ b/apps/wallet/src/locales/pt.locale.ts @@ -548,9 +548,15 @@ export default { join_station_body: 'Entre em contato com o proprietário para obter o ID da Carteira e envie a eles sua identidade para que um usuário possa ser criado para você.', join_station_canister_id: 'ID da carteira', - join_station_name: 'Nome da carteira (opcional)', + join_station_name: 'Nome da carteira', join_station: 'Junte-se a carteira', + station_title: 'Crie a sua própria carteira', + station_body: + 'Crie a sua própria carteira e adicione usuários para gerenciar as suas contas e ativos digitais.', + station_name_field: 'Nome da carteira', + admin_name_field: 'Nome do administrador', + check_permissions_title: 'Verificando o estado da lista de espera ...', join_waitlist_title: 'Junte-se à lista de espera', join_waitlist_body: diff --git a/apps/wallet/src/mappers/requests.mapper.ts b/apps/wallet/src/mappers/requests.mapper.ts index 4f82e8abc..cc2e0c8eb 100644 --- a/apps/wallet/src/mappers/requests.mapper.ts +++ b/apps/wallet/src/mappers/requests.mapper.ts @@ -338,7 +338,7 @@ const mapRequestToUserCsvRow = (request: Request): CsvRow => { if (variantIs(request.operation, 'AddUser')) { return { user_id: request.operation.AddUser.user?.[0]?.id ?? '', - user_name: request.operation.AddUser.input.name?.[0] ?? '', + user_name: request.operation.AddUser.input.name, details: stringify({ identities: request.operation.AddUser.input.identities?.map(i => i.toText()), status: request.operation.AddUser.input.status, @@ -560,7 +560,7 @@ export const mapRequestsToCsvTable = ( const rows = requests.map(entry => { const row: CsvRow = { id: entry.request.id, - requester: entry.additionalInfo?.requester_name[0] ?? entry.request.requested_by, + requester: entry.additionalInfo?.requester_name ?? entry.request.requested_by, status: mapRequestStatusToText(entry.request.status), status_reason: mapRequestStatusToReason(entry.request.status), created: entry.request.created_at, diff --git a/apps/wallet/src/mappers/stations.mapper.ts b/apps/wallet/src/mappers/stations.mapper.ts index 095159e99..34cc74d94 100644 --- a/apps/wallet/src/mappers/stations.mapper.ts +++ b/apps/wallet/src/mappers/stations.mapper.ts @@ -5,6 +5,6 @@ import { Station } from '~/stores/session.store'; export function stationToUserStation(station: Omit): UserStation { return { canister_id: Principal.fromText(station.canisterId), - name: station.name ? [station.name] : [], + name: station.name, }; } diff --git a/apps/wallet/src/pages/UsersPage.vue b/apps/wallet/src/pages/UsersPage.vue index 2bdb465b8..6f10c3c39 100644 --- a/apps/wallet/src/pages/UsersPage.vue +++ b/apps/wallet/src/pages/UsersPage.vue @@ -48,7 +48,7 @@