feat: implement workspace management and update localization strings across multiple languages

This commit is contained in:
Paulo Castellano 2026-05-06 16:41:27 -03:00
parent 7733d573da
commit d83ac0612f
18 changed files with 39 additions and 46 deletions

View file

@ -111,7 +111,6 @@ public function store(StoreWorkspaceRequest $request, LogoAttacher $logoAttacher
}
$validated = $request->validated();
$isFirstWorkspace = ! $user->workspaces()->exists();
$workspace = CreateWorkspace::execute($user, $validated);
@ -127,9 +126,8 @@ public function store(StoreWorkspaceRequest $request, LogoAttacher $logoAttacher
}
}
return $isFirstWorkspace
? redirect()->route('app.accounts')->with('success', __('workspaces.create.first_workspace_success'))
: redirect()->route('app.calendar')->with('success', __('workspaces.create.success'));
return redirect()->route('app.accounts', ['openDialog' => 'true'])
->with('success', __('workspaces.create.success'));
}
public function switch(Request $request, Workspace $workspace): RedirectResponse

View file

@ -82,6 +82,7 @@ public function index(Request $request): Response|RedirectResponse
'filters' => [
'search' => $request->input('search', ''),
],
'openDialog' => $request->boolean('openDialog'),
]);
}

View file

@ -49,8 +49,7 @@
'background_color' => 'Background color',
'text_color' => 'Text color',
'submit' => 'Create workspace',
'first_workspace_success' => 'Workspace created. Connect a social account to start posting.',
'success' => 'Workspace created.',
'success' => 'Workspace created. Connect a social account to start posting.',
],
'limit_reached' => 'You have reached your plan limit for workspaces.',

View file

@ -49,8 +49,7 @@
'background_color' => 'Color de fondo',
'text_color' => 'Color de texto',
'submit' => 'Crear workspace',
'first_workspace_success' => 'Workspace creado. Conecta una cuenta social para empezar a publicar.',
'success' => 'Workspace creado.',
'success' => 'Workspace creado. Conecta una cuenta social para empezar a publicar.',
],
'limit_reached' => 'Has alcanzado el límite de workspaces de tu plan.',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -49,8 +49,7 @@
'background_color' => 'Cor de fundo',
'text_color' => 'Cor do texto',
'submit' => 'Criar workspace',
'first_workspace_success' => 'Workspace criado. Conecte uma conta social para começar a postar.',
'success' => 'Workspace criado.',
'success' => 'Workspace criado. Conecte uma conta social para começar a postar.',
],
'limit_reached' => 'Você atingiu o limite de workspaces do seu plano.',

View file

@ -207,7 +207,7 @@ const huePointerStyle = computed(() => ({
<button
type="button"
:disabled="disabled"
class="size-9 shrink-0 cursor-pointer rounded-md border-2 border-foreground bg-card shadow-xs transition-shadow hover:shadow-sm disabled:cursor-not-allowed disabled:opacity-50"
class="size-10 shrink-0 cursor-pointer rounded-md border-2 border-foreground bg-card shadow-xs transition-shadow hover:shadow-sm disabled:cursor-not-allowed disabled:opacity-50"
:style="swatchColor ? { backgroundColor: swatchColor } : undefined"
:aria-label="placeholder"
/>

View file

@ -21,12 +21,12 @@ export const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
},
size: {
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
"icon": "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
"default": "h-10 px-4 py-2 has-[>svg]:px-3",
"sm": "h-9 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
"lg": "h-11 rounded-md px-6 has-[>svg]:px-4",
"icon": "size-10",
"icon-sm": "size-9",
"icon-lg": "size-11",
},
},
defaultVariants: {

View file

@ -21,7 +21,7 @@ const modelValue = useVModel(props, "modelValue", emits, {
<template>
<input v-model="modelValue" data-slot="input" :class="cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-2 border-foreground h-9 w-full min-w-0 rounded-md bg-card px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-2 border-foreground h-10 w-full min-w-0 rounded-md bg-card px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class,

View file

@ -21,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps)
:data-size="size"
v-bind="forwardedProps"
:class="cn(
'border-2 border-foreground data-[placeholder]:text-foreground/50 [&_svg:not([class*=\'text-\'])]:text-foreground/60 focus-visible:ring-foreground/20 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-lg bg-card px-3.5 py-2 text-sm font-medium text-foreground whitespace-nowrap shadow-2xs transition-shadow outline-none focus:shadow-xs focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
'border-2 border-foreground data-[placeholder]:text-foreground/50 [&_svg:not([class*=\'text-\'])]:text-foreground/60 focus-visible:ring-foreground/20 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-lg bg-card px-3.5 py-2 text-sm font-medium text-foreground whitespace-nowrap shadow-2xs transition-shadow outline-none focus:shadow-xs focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-10 data-[size=sm]:h-9 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
props.class,
)"
>

View file

@ -15,7 +15,7 @@ const delegatedProps = reactiveOmit(props, "class")
data-slot="tabs-list"
v-bind="delegatedProps"
:class="cn(
'inline-flex w-fit items-center gap-2',
'inline-flex h-10 w-fit items-center gap-2',
props.class,
)"
>

View file

@ -16,7 +16,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<TabsTrigger
data-slot="tabs-trigger"
:class="cn(
'inline-flex h-9 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border-2 border-foreground bg-card px-3 text-sm font-bold text-foreground shadow-xs transition-all hover:bg-accent hover:shadow-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-amber-200 data-[state=active]:hover:bg-amber-300 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
'inline-flex h-10 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border-2 border-foreground bg-card px-3 text-sm font-bold text-foreground shadow-xs transition-all hover:bg-accent hover:shadow-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-amber-200 data-[state=active]:hover:bg-amber-300 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
props.class,
)"
v-bind="forwardedProps"

View file

@ -1,17 +1,14 @@
<script setup lang="ts">
import AuthLayout from '@/layouts/auth/AuthSplitLayout.vue';
withDefaults(defineProps<{
defineProps<{
title?: string;
description?: string;
showLegal?: boolean;
}>(), {
showLegal: false,
});
}>();
</script>
<template>
<AuthLayout :title="title" :description="description" :show-legal="showLegal">
<AuthLayout :title="title" :description="description">
<slot />
</AuthLayout>
</template>

View file

@ -10,13 +10,10 @@ import {
import { trans } from 'laravel-vue-i18n';
import { onBeforeUnmount, onMounted, ref, computed } from 'vue';
withDefaults(defineProps<{
defineProps<{
title?: string;
description?: string;
showLegal?: boolean;
}>(), {
showLegal: false,
});
}>();
const slideKeys = ['calendar', 'scheduling', 'media', 'video', 'team', 'signatures'] as const;
@ -112,13 +109,6 @@ const platforms = [
</div>
</div>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div
v-if="showLegal"
class="text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary"
v-html="trans('auth.legal')"
/>
</div>
<div

View file

@ -70,11 +70,12 @@ interface Props {
accounts: ScrollAccounts;
platforms: AvailablePlatform[];
filters: { search: string };
openDialog: boolean;
}
const props = defineProps<Props>();
const isAddDialogOpen = ref(false);
const isAddDialogOpen = ref(props.openDialog);
const deleteModal = ref<InstanceType<typeof ConfirmDeleteModal> | null>(null);
const searchQuery = ref(props.filters.search);

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { Form, Head } from '@inertiajs/vue3';
import { Form, Head, usePage } from '@inertiajs/vue3';
import { IconEye, IconEyeOff } from '@tabler/icons-vue';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import SocialLogin from '@/components/auth/SocialLogin.vue';
import InputError from '@/components/InputError.vue';
@ -28,13 +28,15 @@ defineProps<{
const showPassword = ref(false);
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const page = usePage();
const isSelfHosted = computed(() => Boolean(page.props.selfHosted));
</script>
<template>
<AuthBase
:title="$t('auth.register.title')"
:description="$t('auth.register.description')"
:show-legal="true"
>
<Head :title="$t('auth.register.page_title')" />
@ -135,6 +137,13 @@ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
>
</div>
</Form>
<!-- eslint-disable-next-line vue/no-v-html -->
<div
v-if="!isSelfHosted"
class="text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary"
v-html="$t('auth.legal')"
/>
</div>
</AuthBase>
</template>