Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"prisma.pinToPrisma6": true
}
2 changes: 1 addition & 1 deletion admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@tanstack/vue-table": "^8.21.3",
"@unovis/ts": "^1.6.4",
"@unovis/vue": "^1.6.4",
"@vueuse/core": "^14.2.1",
"@vueuse/core": "^14.3.0",
"axios": "^1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
25 changes: 25 additions & 0 deletions admin/slices/common/components/date/TimeAgo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts" setup>
import { useTimeAgoIntl } from '@vueuse/core';

const { date, class: className } = defineProps<{
date: string;
class?: string;
}>();
const { locale } = useI18n();
// Getter (not a plain Date) so it re-evaluates if `date` changes.
const timeAgoIntl = useTimeAgoIntl(() => new Date(date || Date.now()), { locale: locale.value });
const timeAgo = computed(() => (date ? timeAgoIntl.value : ''));
</script>
<template>
<div :class="cn('flex flex-col items-end leading-none', className)">
<span class="text-right">{{ formatDateTime(date) }} </span>
<slot v-if="date" name="value" :value="timeAgo">
<span class="text-xs text-muted-foreground">
{{ timeAgo }}
</span>
</slot>
</div>
</template>


<style></style>
12 changes: 12 additions & 0 deletions admin/slices/common/components/date/TimeAt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
const props = defineProps<{
date?: string | null;
}>();
</script>

<template>
<div class="flex flex-col items-end text-xs">
<TimeAgo v-if="date" :date="date" />
<span v-else class="text-muted-foreground">—</span>
</div>
</template>
4 changes: 4 additions & 0 deletions admin/slices/common/utils/formatDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const formatDateTime = (date?: string) => {
if (!date) return '';
return new Date(date).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' });
};
1 change: 1 addition & 0 deletions admin/slices/common/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './formatDate';
35 changes: 21 additions & 14 deletions admin/slices/setup/api/data/repositories/api/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,11 @@ export const SetAgentChannelsDtoSchema = {
required: ["channels"],
} as const;

export const AssignableUserRoleTypesSchema = {
type: "string",
enum: ["Admin", "User"],
} as const;

export const CreateUserDtoSchema = {
type: "object",
properties: {
Expand All @@ -1568,12 +1573,13 @@ export const CreateUserDtoSchema = {
example: "strongPassword1",
minLength: 8,
},
roles: {
type: "array",
example: ["User"],
items: {
$ref: "#/components/schemas/UserRoleTypes",
},
role: {
example: "User",
allOf: [
{
$ref: "#/components/schemas/AssignableUserRoleTypes",
},
],
},
},
required: ["name", "email", "password"],
Expand Down Expand Up @@ -1602,18 +1608,19 @@ export const UpdateUserDtoSchema = {
},
} as const;

export const UpdateUserRolesDtoSchema = {
export const UpdateUserRoleDtoSchema = {
type: "object",
properties: {
roles: {
type: "array",
example: ["User"],
items: {
$ref: "#/components/schemas/UserRoleTypes",
},
role: {
example: "User",
allOf: [
{
$ref: "#/components/schemas/AssignableUserRoleTypes",
},
],
},
},
required: ["roles"],
required: ["role"],
} as const;

export const SaveTemplateFileDtoSchema = {
Expand Down
14 changes: 7 additions & 7 deletions admin/slices/setup/api/data/repositories/api/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ import type {
UserControllerRemoveData,
UserControllerFindByIdData,
UserControllerUpdateData,
UserControllerUpdateRolesData,
UserControllerUpdateRoleData,
TemplateFileControllerListData,
TemplateFileControllerReadData,
TemplateFileControllerSaveData,
Expand Down Expand Up @@ -2300,7 +2300,7 @@ export class UsersService {
}

/**
* Invite a new user
* Create a user (admin sets the password)
*/
public static userControllerCreate<ThrowOnError extends boolean = false>(
options: Options<UserControllerCreateData, ThrowOnError>,
Expand Down Expand Up @@ -2352,7 +2352,7 @@ export class UsersService {
}

/**
* Update user (name, email, password, status). Use /roles to change roles.
* Update user (name, email, password, status). Use /role to change the role.
*/
public static userControllerUpdate<ThrowOnError extends boolean = false>(
options: Options<UserControllerUpdateData, ThrowOnError>,
Expand All @@ -2372,17 +2372,17 @@ export class UsersService {
}

/**
* Replace the user's roles. Owner only.
* Set the user's role. Owner only.
*/
public static userControllerUpdateRoles<ThrowOnError extends boolean = false>(
options: Options<UserControllerUpdateRolesData, ThrowOnError>,
public static userControllerUpdateRole<ThrowOnError extends boolean = false>(
options: Options<UserControllerUpdateRoleData, ThrowOnError>,
) {
return (options.client ?? _heyApiClient).put<
unknown,
unknown,
ThrowOnError
>({
url: "/users/{id}/roles",
url: "/users/{id}/role",
...options,
headers: {
"Content-Type": "application/json",
Expand Down
19 changes: 12 additions & 7 deletions admin/slices/setup/api/data/repositories/api/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,11 +718,16 @@ export type SetAgentChannelsDto = {
channels: Array<AgentChannelDto>;
};

export enum AssignableUserRoleTypes {
ADMIN = "Admin",
USER = "User",
}

export type CreateUserDto = {
name: string;
email: string;
password: string;
roles?: Array<UserRoleTypes>;
role?: AssignableUserRoleTypes;
};

export type UpdateUserDto = {
Expand All @@ -732,8 +737,8 @@ export type UpdateUserDto = {
status?: "active" | "invited" | "disabled";
};

export type UpdateUserRolesDto = {
roles: Array<UserRoleTypes>;
export type UpdateUserRoleDto = {
role: AssignableUserRoleTypes;
};

export type SaveTemplateFileDto = {
Expand Down Expand Up @@ -2914,16 +2919,16 @@ export type UserControllerUpdateResponses = {
200: unknown;
};

export type UserControllerUpdateRolesData = {
body: UpdateUserRolesDto;
export type UserControllerUpdateRoleData = {
body: UpdateUserRoleDto;
path: {
id: string;
};
query?: never;
url: "/users/{id}/roles";
url: "/users/{id}/role";
};

export type UserControllerUpdateRolesResponses = {
export type UserControllerUpdateRoleResponses = {
200: unknown;
};

Expand Down
24 changes: 24 additions & 0 deletions admin/slices/setup/theme/components/ui/radio-group/RadioGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import type { RadioGroupRootEmits, RadioGroupRootProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { RadioGroupRoot, useForwardPropsEmits } from "reka-ui"
import { cn } from '#theme/utils'

const props = defineProps<RadioGroupRootProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<RadioGroupRootEmits>()

const delegatedProps = reactiveOmit(props, "class")

const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<RadioGroupRoot
data-slot="radio-group"
v-bind="forwarded"
:class="cn('grid gap-3', props.class)"
>
<slot />
</RadioGroupRoot>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { RadioGroupItemProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { Circle } from "lucide-vue-next"
import { RadioGroupIndicator, RadioGroupItem, useForwardProps } from "reka-ui"
import { cn } from '#theme/utils'

const props = defineProps<RadioGroupItemProps & { class?: HTMLAttributes["class"] }>()

const delegatedProps = reactiveOmit(props, "class")

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
<RadioGroupItem
data-slot="radio-group-item"
v-bind="forwardedProps"
:class="
cn('border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
props.class)"
>
<RadioGroupIndicator
data-slot="radio-group-indicator"
class="relative flex items-center justify-center"
>
<Circle class="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupIndicator>
</RadioGroupItem>
</template>
2 changes: 2 additions & 0 deletions admin/slices/setup/theme/components/ui/radio-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as RadioGroup } from "./RadioGroup.vue"
export { default as RadioGroupItem } from "./RadioGroupItem.vue"
6 changes: 2 additions & 4 deletions admin/slices/user/auth/data/auth.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const EMPTY_USER: IAuthUser = {
id: '',
name: '',
email: '',
roles: [],
role: '',
status: '',
};

Expand All @@ -22,9 +22,7 @@ export class AuthMapper {
id: o.id,
name: str(o.name),
email: str(o.email),
roles: Array.isArray(o.roles)
? o.roles.filter((r): r is string => typeof r === 'string')
: [],
role: str(o.role),
status: str(o.status),
};
}
Expand Down
2 changes: 1 addition & 1 deletion admin/slices/user/auth/domain/auth.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface IAuthUser {
id: string;
name: string;
email: string;
roles: string[];
role: string;
status: string;
}

Expand Down
4 changes: 1 addition & 3 deletions admin/slices/user/auth/stores/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export const useAuthStore = defineStore('auth', () => {

const isAuthenticated = computed(() => !!accessToken.value);
const hasAdminAccess = computed(() =>
!!user.value?.roles?.some((role) =>
(ADMIN_ROLES as readonly string[]).includes(role),
),
(ADMIN_ROLES as readonly string[]).includes(user.value?.role ?? ''),
);

function applyToken(token: string | null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ICreateUserData } from '#user/stores/user';
import type { ICreateUserData } from '#user/domain/user.types';
import { IconArrowLeft } from '@tabler/icons-vue';

const userStore = useUserStore();
Expand Down Expand Up @@ -27,13 +27,12 @@ function onCancel() {
</NuxtLink>

<div>
<h1 class="text-2xl font-semibold">Invite user</h1>
<h1 class="text-2xl font-semibold">Create user</h1>
<p class="text-sm text-muted-foreground">Grant access to this workspace.</p>
</div>

<UserForm
<UserItemForm
:submitting="submitting"
submit-label="Send invite"
@submit="onSubmit"
@cancel="onCancel"
/>
Expand Down
Loading
Loading