Compare commits

..

3 Commits

Author SHA1 Message Date
Matti Nannt
adcc596875 chore: update 2.7 migration guide (#4256) 2024-11-08 12:37:15 +01:00
Dhruwang Jariwala
1bcdf06b43 fix: slack integration crashing (#4254) 2024-11-08 09:26:27 +00:00
Matti Nannt
3be78f0312 chore: v2.7 release preparation (#4249) 2024-11-08 10:35:49 +01:00
27 changed files with 202 additions and 114 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -8,10 +8,7 @@ import LinkWithQuestions from "./images/link-with-questions.webp";
import ListLinkedSurveys from "./images/list-linked-surveys.webp";
import SlackAuth from "./images/slack-auth.webp";
import SlackConnected from "./images/slack-connected.webp";
import AddSlackBot1 from "./images/add-slack-bot-1.webp";
import AddSlackBot2 from "./images/add-slack-bot-2.webp";
import AddSlackBot3 from "./images/add-slack-bot-3.webp";
import AddSlackBot4 from "./images/add-slack-bot-4.webp";
export const metadata = {
title: "Slack",
description:
@@ -72,34 +69,7 @@ The slack integration allows you to automatically send responses to a Slack chan
channel in the Slack workspace you integrated.
</Note>
5. In order to make your channel available in channel dropdown, you need to add formbricks integration bot to the channel you want to link. You can do this by going to channel settings -> Integrations -> Add apps -> Search for "Formbricks" -> Select the bot -> Add.
<MdxImage
src={AddSlackBot1}
alt="Click on three dot at top right of the channel"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot2}
alt="Select Edit Settings"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot3}
alt="Navigate to Integrations"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot4}
alt="Add Formbricks Bot"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
6. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
5. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
<MdxImage
src={LinkSurveyWithChannel}
@@ -108,7 +78,7 @@ The slack integration allows you to automatically send responses to a Slack chan
className="max-w-full rounded-lg sm:max-w-3xl"
/>
7. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
6. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
<MdxImage
src={LinkWithQuestions}
@@ -117,7 +87,7 @@ The slack integration allows you to automatically send responses to a Slack chan
className="max-w-full rounded-lg sm:max-w-3xl"
/>
8. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
7. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
<MdxImage
src={ListLinkedSurveys}
@@ -154,7 +124,6 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
- `channels:read`
- `groups:read`
- `chat:write`
- `chat:write.public`
- `chat:write.customize`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -6,7 +6,7 @@ import IndvInvite from "./images/individual-invite.webp";
import MenuItem from "./images/organization-settings-menu.webp";
export const metadata = {
title: "Organization Access Roles",
title: "User Management",
description:
"Assign different roles to organization members to grant them specific rights like creating surveys, viewing responses, or managing organization members.",
};
@@ -134,7 +134,7 @@ There are two ways to invite organization members: One by one or in bulk.
<Note>
Access Roles is a feature of the **Enterprise Edition**. In the **Community Edition** and on the **Free**
and **Startup** plan in the Cloud you can invite unlimited organization members as `Admins`.
and **Startup** plan in the Cloud you can invite unlimited organization members as `Owners`.
</Note>
Formbricks sends an email to the organization member with an invitation link. The organization member can accept the invitation or create a new account by clicking on the link.

View File

@@ -302,7 +302,6 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
- `channels:read`
- `groups:read`
- `chat:write`
- `chat:write.public`
- `chat:write.customize`

View File

@@ -8,6 +8,123 @@ export const metadata = {
# Migration Guide
## v2.7
<Note>
This release sets the foundation for our upcoming AI features, currently in private beta. Formbricks now
requires the `pgvector` extension to be installed in the PostgreSQL database. For users of our one-click
setup, simply use the `pgvector/pgvector:pg15` image instead of `postgres:15-alpine`.
</Note>
Formbricks v2.7 includes all the features and improvements developed by the community during hacktoberfest 2024. Additionally we introduce an advanced team-based access control system (requires Formbricks Enterprise Edition).
### Additional Updates
If you previously used organization-based access control (enterprise feature) as well as the `DEFAULT_ORGANIZATION_ROLE` environment variable, make sure to update the value to one of the following roles: `owner`, `manager`, `member`. Read more about the new roles in the [Docs](/global/access-roles).
### Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly.
To run all these steps, please navigate to the `formbricks` folder where your `docker-compose.yml` file is located.
1. **Backup your Database**: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
<Col>
<CodeGroup title="Backup Postgres">
```bash
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.7_$(date +%Y%m%d_%H%M%S).dump
```
</CodeGroup>
</Col>
<Note>
If you run into “No such container”, use `docker ps` to find your container name, e.g.
`formbricks_postgres_1`.
</Note>
<Note>
If you prefer storing the backup as an `*.sql` file remove the `-Fc` (custom format) option. In case of a
restore scenario you will need to use `psql` then with an empty `formbricks` database.
</Note>
2. If you use an older `docker-compose.yml` file from the one-click setup, modify it to use the `pgvector/pgvector:pg15` image instead of `postgres:15-alpine`:
```yaml
services:
postgres:
image: pgvector/pgvector:pg15
volumes:
- postgres:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432:5432
```
3. Pull the latest version of Formbricks:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker compose pull
```
</CodeGroup>
</Col>
4. Stop the running Formbricks instance & remove the related containers:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker compose down
```
</CodeGroup>
</Col>
5. Restarting the containers with the latest version of Formbricks:
<Col>
<CodeGroup title="Restart the containers">
```bash
docker compose up -d
```
</CodeGroup>
</Col>
6. Now let's migrate the data to the latest schema:
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
<Col>
<CodeGroup title="Migrate the data">
```bash
docker pull ghcr.io/formbricks/data-migrations:latest && \
docker run --rm \
--network=formbricks_default \
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
-e UPGRADE_TO_VERSION="v2.7" \
ghcr.io/formbricks/data-migrations:v2.7.0
```
</CodeGroup>
</Col>
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
7. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
## v2.6
Formbricks v2.6 introduces advanced logic jumps for surveys, allowing you to add more advanced branching logic to your surveys including variables, and/or conditions and many more. This release also includes a lot of bug fixes, big performance improvements to website and app surveys and a lot of stability improvements.

View File

@@ -126,7 +126,7 @@ export const navigation: Array<NavGroup> = [
{ title: "Zapier", href: "/developer-docs/integrations/zapier" },
],
},
{ title: "Organization and User Management", href: "/global/access-roles" },
{ title: "User Management", href: "/global/access-roles" },
{ title: "Styling Theme", href: "/global/styling-theme" },
],
},

View File

@@ -337,7 +337,7 @@ export const SurveyMenuBar = ({
/>
</div>
{responseCount > 0 && (
<div className="flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<div className="ju flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger>

View File

@@ -7,12 +7,12 @@ import { z } from "zod";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { ZId } from "@formbricks/types/common";
const ZGetSlackChannelsAction = z.object({
const ZRefreshChannelsAction = z.object({
environmentId: ZId,
});
export const getSlackChannelsAction = authenticatedActionClient
.schema(ZGetSlackChannelsAction)
export const refreshChannelsAction = authenticatedActionClient
.schema(ZRefreshChannelsAction)
.action(async ({ ctx, parsedInput }) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,

View File

@@ -23,8 +23,6 @@ interface ManageIntegrationProps {
React.SetStateAction<(TIntegrationSlackConfigData & { index: number }) | null>
>;
refreshChannels: () => void;
showReconnectButton: boolean;
handleSlackAuthorization: () => void;
locale: TUserLocale;
}
@@ -35,8 +33,6 @@ export const ManageIntegration = ({
setIsConnected,
setSelectedIntegration,
refreshChannels,
showReconnectButton,
handleSlackAuthorization,
locale,
}: ManageIntegrationProps) => {
const t = useTranslations();
@@ -74,18 +70,7 @@ export const ManageIntegration = ({
return (
<div className="mt-6 flex w-full flex-col items-center justify-center p-6">
{showReconnectButton && (
<div className="mb-4 flex w-full items-center justify-between space-x-4">
<p className="text-amber-700">
<strong>Note:</strong> We recently changed our Slack integration to also support private channels.
Please reconnect your Slack workspace.
</p>
<Button onClick={handleSlackAuthorization} variant="secondary">
Reconnect
</Button>
</div>
)}
<div className="flex w-full justify-end space-x-4">
<div className="flex w-full justify-end">
<div className="mr-6 flex items-center">
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
<span className="text-slate-500">

View File

@@ -1,11 +1,11 @@
"use client";
import { getSlackChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { refreshChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { AddChannelMappingModal } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal";
import { ManageIntegration } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/ManageIntegration";
import { authorize } from "@/app/(app)/environments/[environmentId]/integrations/slack/lib/slack";
import slackLogo from "@/images/slacklogo.png";
import { useEffect, useState } from "react";
import { useState } from "react";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
@@ -18,6 +18,7 @@ interface SlackWrapperProps {
isEnabled: boolean;
environment: TEnvironment;
surveys: TSurvey[];
channelsArray: TIntegrationItem[];
slackIntegration?: TIntegrationSlack;
webAppUrl: string;
attributeClasses: TAttributeClass[];
@@ -28,34 +29,27 @@ export const SlackWrapper = ({
isEnabled,
environment,
surveys,
channelsArray,
slackIntegration,
webAppUrl,
attributeClasses,
locale,
}: SlackWrapperProps) => {
const [isConnected, setIsConnected] = useState(slackIntegration ? slackIntegration.config?.key : false);
const [slackChannels, setSlackChannels] = useState<TIntegrationItem[]>([]);
const [slackChannels, setSlackChannels] = useState(channelsArray);
const [isModalOpen, setModalOpen] = useState<boolean>(false);
const [showReconnectButton, setShowReconnectButton] = useState<boolean>(false);
const [selectedIntegration, setSelectedIntegration] = useState<
(TIntegrationSlackConfigData & { index: number }) | null
>(null);
const getSlackChannels = async () => {
const getSlackChannelsResponse = await getSlackChannelsAction({ environmentId: environment.id });
if (getSlackChannelsResponse?.serverError && getSlackChannelsResponse?.serverError === "missing_scope") {
setShowReconnectButton(true);
}
const refreshChannels = async () => {
const refreshChannelsResponse = await refreshChannelsAction({ environmentId: environment.id });
if (getSlackChannelsResponse?.data) {
setSlackChannels(getSlackChannelsResponse.data);
if (refreshChannelsResponse?.data) {
setSlackChannels(refreshChannelsResponse.data);
}
};
useEffect(() => {
getSlackChannels();
}, []);
const handleSlackAuthorization = async () => {
authorize(environment.id, webAppUrl).then((url: string) => {
if (url) {
@@ -82,9 +76,7 @@ export const SlackWrapper = ({
setOpenAddIntegrationModal={setModalOpen}
setIsConnected={setIsConnected}
setSelectedIntegration={setSelectedIntegration}
refreshChannels={getSlackChannels}
showReconnectButton={showReconnectButton}
handleSlackAuthorization={handleSlackAuthorization}
refreshChannels={refreshChannels}
locale={locale}
/>
</>

View File

@@ -12,8 +12,10 @@ import { getIntegrationByType } from "@formbricks/lib/integration/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
@@ -43,6 +45,10 @@ const Page = async ({ params }) => {
throw new Error(t("common.product_not_found"));
}
let channelsArray: TIntegrationItem[] = [];
if (slackIntegration && slackIntegration.config.key) {
channelsArray = await getSlackChannels(params.environmentId);
}
const locale = await findMatchingLocale();
const currentUserMembership = await getMembershipByUserIdOrganizationId(
@@ -64,11 +70,12 @@ const Page = async ({ params }) => {
return (
<PageContentWrapper>
<GoBackButton url={`${WEBAPP_URL}/environments/${params.environmentId}/integrations`} />
<PageHeader pageTitle={"environments.integrations.slack.slack_integration"} />
<PageHeader pageTitle={t("environments.integrations.slack.slack_integration")} />
<div className="h-[75vh] w-full">
<SlackWrapper
isEnabled={isEnabled}
environment={environment}
channelsArray={channelsArray}
surveys={surveys}
slackIntegration={slackIntegration as TIntegrationSlack}
webAppUrl={WEBAPP_URL}

View File

@@ -1,12 +1,8 @@
import { responses } from "@/app/lib/api/response";
import { NextRequest } from "next/server";
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { createOrUpdateIntegration, getIntegrationByType } from "@formbricks/lib/integration/service";
import {
TIntegrationSlackConfig,
TIntegrationSlackConfigData,
TIntegrationSlackCredential,
} from "@formbricks/types/integration/slack";
import { createOrUpdateIntegration } from "@formbricks/lib/integration/service";
import { TIntegrationSlackConfig, TIntegrationSlackCredential } from "@formbricks/types/integration/slack";
export const GET = async (req: NextRequest) => {
const url = req.url;
@@ -26,19 +22,33 @@ export const GET = async (req: NextRequest) => {
if (!SLACK_CLIENT_ID) return responses.internalServerErrorResponse("Slack client id is missing");
if (!SLACK_CLIENT_SECRET) return responses.internalServerErrorResponse("Slack client secret is missing");
const formData = new FormData();
formData.append("code", code ?? "");
formData.append("client_id", SLACK_CLIENT_ID ?? "");
formData.append("client_secret", SLACK_CLIENT_SECRET ?? "");
const formData = {
code,
client_id: SLACK_CLIENT_ID,
client_secret: SLACK_CLIENT_SECRET,
};
const formBody: string[] = [];
for (const property in formData) {
const encodedKey = encodeURIComponent(property);
const encodedValue = encodeURIComponent(formData[property]);
formBody.push(encodedKey + "=" + encodedValue);
}
const bodyString = formBody.join("&");
if (code) {
const response = await fetch("https://slack.com/api/oauth.v2.access", {
method: "POST",
body: formData,
body: bodyString,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
const data = await response.json();
if (!data.ok) {
return responses.badRequestResponse(data.error);
}
const slackCredentials: TIntegrationSlackCredential = {
app_id: data.app_id,
authed_user: data.authed_user,
@@ -48,20 +58,18 @@ export const GET = async (req: NextRequest) => {
team: data.team,
};
const slackIntegration = await getIntegrationByType(environmentId, "slack");
const slackConfiguration: TIntegrationSlackConfig = {
data: (slackIntegration?.config.data as TIntegrationSlackConfigData[]) ?? [],
data: [],
key: slackCredentials,
};
const integration = {
const slackIntegration = {
type: "slack" as "slack",
environment: environmentId,
config: slackConfiguration,
};
const result = await createOrUpdateIntegration(environmentId, integration);
const result = await createOrUpdateIntegration(environmentId, slackIntegration);
if (result) {
return Response.redirect(`${WEBAPP_URL}/environments/${environmentId}/integrations/slack`);

View File

@@ -22,6 +22,7 @@ export const actionClient = createSafeActionClient({
}
console.error("SERVER ERROR: ", e);
return DEFAULT_SERVER_ERROR_MESSAGE;
},
});

View File

@@ -1,6 +1,7 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { AccessView } from "@/modules/ee/teams/product-teams/components/access-view";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
@@ -12,6 +13,7 @@ import { PageHeader } from "@formbricks/ui/components/PageHeader";
import { getTeamsByOrganizationId, getTeamsByProductId } from "./lib/teams";
export const ProductTeams = async ({ params }: { params: { environmentId: string } }) => {
const t = await getTranslations();
const [product, session, organization] = await Promise.all([
getProductByEnvironmentId(params.environmentId),
getServerSession(authOptions),
@@ -19,13 +21,13 @@ export const ProductTeams = async ({ params }: { params: { environmentId: string
]);
if (!product) {
throw new Error("Product not found");
throw new Error(t("common.product_not_found"));
}
if (!session) {
throw new Error("Unauthorized");
throw new Error(t("common.session_not_found"));
}
if (!organization) {
throw new Error("Organization not found");
throw new Error(t("common.organization_not_found"));
}
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
@@ -37,20 +39,20 @@ export const ProductTeams = async ({ params }: { params: { environmentId: string
const teams = await getTeamsByProductId(product.id);
if (!teams) {
throw new Error("Teams not found");
throw new Error(t("common.teams_not_found"));
}
const organizationTeams = await getTeamsByOrganizationId(organization.id);
if (!organizationTeams) {
throw new Error("Organization Teams not found");
throw new Error(t("common.organization_teams_not_found"));
}
const isOwnerOrManager = isOwner || isManager;
return (
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<PageHeader pageTitle={t("common.configuration")}>
<ProductConfigNavigation
environmentId={params.environmentId}
activeId="teams"

View File

@@ -2,6 +2,7 @@ import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmen
import { TeamsView } from "@/modules/ee/teams/team-list/components/teams-view";
import { getTeams } from "@/modules/ee/teams/team-list/lib/teams";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
import { authOptions } from "@formbricks/lib/authOptions";
@@ -13,14 +14,15 @@ import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper
import { PageHeader } from "@formbricks/ui/components/PageHeader";
export const TeamsPage = async ({ params }) => {
const t = await getTranslations();
const session = await getServerSession(authOptions);
if (!session) {
throw new Error("Unauthenticated");
throw new Error(t("common.session_not_found"));
}
const organization = await getOrganizationByEnvironmentId(params.environmentId);
if (!organization) {
throw new Error("Organization not found");
throw new Error(t("common.organization_not_found"));
}
const canDoRoleManagement = await getRoleManagementPermission(organization);
@@ -34,12 +36,12 @@ export const TeamsPage = async ({ params }) => {
const teams = await getTeams(session.user.id, organization.id);
if (!teams) {
throw new Error("Teams not found");
throw new Error(t("common.teams_not_found"));
}
return (
<PageContentWrapper>
<PageHeader pageTitle="Organization Settings">
<PageHeader pageTitle={t("environments.settings.general.organization_settings")}>
<OrganizationSettingsNavbar
environmentId={params.environmentId}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}

View File

@@ -1,6 +1,6 @@
{
"name": "@formbricks/web",
"version": "2.6.0",
"version": "2.7.0",
"private": true,
"scripts": {
"clean": "rimraf .turbo node_modules .next",

View File

@@ -54,7 +54,8 @@
"data-migration:segments-actions-cleanup": "ts-node ./data-migrations/20240904091113_removed_actions_table/data-migration.ts",
"data-migration:migrate-survey-types": "ts-node ./data-migrations/20241002123456_migrate_survey_types/data-migration.ts",
"data-migration:v2.6": "pnpm data-migration:add-display-id-to-response && pnpm data-migration:address-question && pnpm data-migration:advanced-logic && pnpm data-migration:segments-actions-cleanup && pnpm data-migration:migrate-survey-types",
"data-migration:add-teams": "ts-node ./data-migrations/20241107161932_add_teams/data-migration.ts"
"data-migration:add-teams": "ts-node ./data-migrations/20241107161932_add_teams/data-migration.ts",
"data-migration:v2.7": "pnpm data-migration:add-teams"
},
"dependencies": {
"@prisma/client": "5.20.0",

View File

@@ -53,7 +53,7 @@ export const INVITE_DISABLED = env.INVITE_DISABLED === "1";
export const SLACK_CLIENT_SECRET = env.SLACK_CLIENT_SECRET;
export const SLACK_CLIENT_ID = env.SLACK_CLIENT_ID;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize,groups:read`;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize`;
export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID;
export const GOOGLE_SHEETS_CLIENT_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET;

View File

@@ -367,7 +367,9 @@
"targeting": "Targeting",
"team": "Team",
"team_access": "Teamzugriff",
"team_not_found": "Team nicht gefunden",
"teams": "Teams",
"teams_not_found": "Teams nicht gefunden",
"text": "Text",
"time": "Zeit",
"time_to_finish": "Zeit zum Fertigstellen",

View File

@@ -367,7 +367,9 @@
"targeting": "Targeting",
"team": "Team",
"team_access": "Team Access",
"team_not_found": "Team not found",
"teams": "Teams",
"teams_not_found": "Teams not found",
"text": "Text",
"time": "Time",
"time_to_finish": "Time to finish",

View File

@@ -367,7 +367,9 @@
"targeting": "mirando",
"team": "Time",
"team_access": "Acesso da equipe",
"team_not_found": "Equipe não encontrada",
"teams": "Times",
"teams_not_found": "Equipes não encontradas",
"text": "Texto",
"time": "tempo",
"time_to_finish": "Hora de terminar",

View File

@@ -11,9 +11,8 @@ export const fetchChannels = async (slackIntegration: TIntegration): Promise<TIn
let nextCursor: string | undefined = undefined;
do {
const url = new URL("https://slack.com/api/users.conversations");
const url = new URL("https://slack.com/api/conversations.list");
url.searchParams.append("limit", "200");
url.searchParams.append("types", "private_channel,public_channel");
if (nextCursor) {
url.searchParams.append("cursor", nextCursor);
}

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Badge } from "./index";
import { BadgeSelect } from "./index";
const meta = {
title: "ui/Badge",
component: Badge,
title: "ui/BadgeSelect",
component: BadgeSelect,
tags: ["autodocs"],
parameters: {
layout: "centered",
@@ -16,7 +16,7 @@ const meta = {
size: { control: "select", options: ["small", "normal", "large"] },
className: { control: "text" },
},
} satisfies Meta<typeof Badge>;
} satisfies Meta<typeof BadgeSelect>;
export default meta;