B2B-87: add company statistics consent (#29)
* B2B-87: add company statistics consent * add toggle for company statistics consent under profile * add toggle for company statistics consent under profile * add audit logging to accounts * change policy * add audit logging to accounts * remove full account data query and just query the entire account every time * add comment about consent toggle * make constants hardcoded, as dynamic ones do not work * add back pending check --------- Co-authored-by: Helena <helena@Helenas-MacBook-Pro.local>
This commit is contained in:
@@ -13,10 +13,10 @@ import { personalAccountNavigationConfig } from '~/config/personal-account-navig
|
|||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
// home imports
|
// home imports
|
||||||
import { HomeMenuNavigation } from './_components/home-menu-navigation';
|
import { HomeMenuNavigation } from '../_components/home-menu-navigation';
|
||||||
import { HomeMobileNavigation } from './_components/home-mobile-navigation';
|
import { HomeMobileNavigation } from '../_components/home-mobile-navigation';
|
||||||
import { HomeSidebar } from './_components/home-sidebar';
|
import { HomeSidebar } from '../_components/home-sidebar';
|
||||||
import { loadUserWorkspace } from './_lib/server/load-user-workspace';
|
import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
|
||||||
|
|
||||||
function UserHomeLayout({ children }: React.PropsWithChildren) {
|
function UserHomeLayout({ children }: React.PropsWithChildren) {
|
||||||
const state = use(getLayoutState());
|
const state = use(getLayoutState());
|
||||||
@@ -58,8 +58,8 @@ function HeaderLayout({ children }: React.PropsWithChildren) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserWorkspaceContextProvider value={workspace}>
|
<UserWorkspaceContextProvider value={workspace}>
|
||||||
<Page style={'header'} >
|
<Page style={'header'}>
|
||||||
<PageNavigation >
|
<PageNavigation>
|
||||||
<HomeMenuNavigation workspace={workspace} />
|
<HomeMenuNavigation workspace={workspace} />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
@@ -3,11 +3,11 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
import Dashboard from './_components/dashboard';
|
import Dashboard from '../_components/dashboard';
|
||||||
// local imports
|
// local imports
|
||||||
import { HomeLayoutPageHeader } from './_components/home-page-header';
|
import { HomeLayoutPageHeader } from '../_components/home-page-header';
|
||||||
import { use } from 'react';
|
import { use } from 'react';
|
||||||
import { loadUserWorkspace } from './_lib/server/load-user-workspace';
|
import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
|
||||||
import { PageBody } from '@kit/ui/page';
|
import { PageBody } from '@kit/ui/page';
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
73
app/home/(user)/_components/consent-dialog.tsx
Normal file
73
app/home/(user)/_components/consent-dialog.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
|
import { useRevalidatePersonalAccountDataQuery } from '@kit/accounts/hooks/use-personal-account-data';
|
||||||
|
import { useUpdateAccountData } from '@kit/accounts/hooks/use-update-account';
|
||||||
|
import { Button } from '@kit/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@kit/ui/dialog';
|
||||||
|
import { toast } from '@kit/ui/sonner';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
export default function ConsentDialog({ userId }: { userId: string }) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const updateAccountMutation = useUpdateAccountData(userId);
|
||||||
|
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
|
||||||
|
|
||||||
|
const updateConsent = (consentToCompanyStatistics: boolean) => {
|
||||||
|
const promise = updateAccountMutation
|
||||||
|
.mutateAsync({
|
||||||
|
has_consent_anonymized_company_statistics: consentToCompanyStatistics,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
revalidateUserDataQuery(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.promise(() => promise, {
|
||||||
|
success: <Trans i18nKey={'account:updateConsentSuccess'} />,
|
||||||
|
error: <Trans i18nKey={'account:updateConsentError'} />,
|
||||||
|
loading: <Trans i18nKey={'account:updateConsentLoading'} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
return router.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog defaultOpen>
|
||||||
|
<DialogContent
|
||||||
|
className="flex max-w-[436px] flex-col items-center gap-4 space-y-4"
|
||||||
|
disableClose
|
||||||
|
>
|
||||||
|
<DialogHeader className="items-center text-center">
|
||||||
|
<Image alt="Toggle" src="/assets/toggle.png" width={96} height={96} />
|
||||||
|
<DialogTitle>
|
||||||
|
<Trans i18nKey="account:consentModal.title" />
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<Trans i18nKey="account:consentModal.description" />
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="secondary" onClick={() => updateConsent(false)}>
|
||||||
|
<Trans i18nKey="account:consentModal.reject" />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => updateConsent(true)}>
|
||||||
|
<Trans i18nKey="account:consentModal.accept" />
|
||||||
|
<CaretRightIcon />
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { AppLogo } from '~/components/app-logo';
|
|||||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
||||||
import { Search } from '~/components/ui/search';
|
import { Search } from '~/components/ui/search';
|
||||||
|
|
||||||
import { SIDEBAR_WIDTH } from '../../../../packages/ui/src/shadcn/constants';
|
import { SIDEBAR_WIDTH_PROPERTY } from '../../../../packages/ui/src/shadcn/constants';
|
||||||
// home imports
|
// home imports
|
||||||
import { UserNotifications } from '../_components/user-notifications';
|
import { UserNotifications } from '../_components/user-notifications';
|
||||||
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||||
@@ -17,7 +17,7 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 items-center justify-between gap-3'}>
|
<div className={'flex w-full flex-1 items-center justify-between gap-3'}>
|
||||||
<div className={`flex items-center w-[${SIDEBAR_WIDTH}]`}>
|
<div className={`flex items-center ${SIDEBAR_WIDTH_PROPERTY}`}>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
21
app/home/(user)/_lib/server/load-user-account.ts
Normal file
21
app/home/(user)/_lib/server/load-user-account.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { cache } from 'react';
|
||||||
|
|
||||||
|
import { createAccountsApi } from '@kit/accounts/api';
|
||||||
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
|
export type UserAccount = Awaited<ReturnType<typeof loadUserAccount>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name loadUserAccount
|
||||||
|
* @description
|
||||||
|
* Load the user account. It's a cached per-request function that fetches the user workspace data.
|
||||||
|
* It can be used across the server components to load the user workspace data.
|
||||||
|
*/
|
||||||
|
export const loadUserAccount = cache(accountLoader);
|
||||||
|
|
||||||
|
async function accountLoader(accountId: string) {
|
||||||
|
const client = getSupabaseServerClient();
|
||||||
|
const api = createAccountsApi(client);
|
||||||
|
|
||||||
|
return api.getAccount(accountId);
|
||||||
|
}
|
||||||
24
app/home/layout.tsx
Normal file
24
app/home/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { requireUserInServerComponent } from '../../lib/server/require-user-in-server-component';
|
||||||
|
import ConsentDialog from './(user)/_components/consent-dialog';
|
||||||
|
import { loadUserAccount } from './(user)/_lib/server/load-user-account';
|
||||||
|
|
||||||
|
export default async function HomeLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const user = await requireUserInServerComponent();
|
||||||
|
const account = user?.identities?.[0]?.id
|
||||||
|
? await loadUserAccount(user?.identities?.[0]?.id)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (account && account?.has_consent_anonymized_company_statistics === null) {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<ConsentDialog userId={user.id} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
|
|
||||||
import { usePersonalAccountData } from '../../hooks/use-personal-account-data';
|
import { usePersonalAccountData } from '../../hooks/use-personal-account-data';
|
||||||
import { AccountDangerZone } from './account-danger-zone';
|
import { AccountDangerZone } from './account-danger-zone';
|
||||||
|
import ConsentToggle from './consent/consent-toggle';
|
||||||
import { UpdateEmailFormContainer } from './email/update-email-form-container';
|
import { UpdateEmailFormContainer } from './email/update-email-form-container';
|
||||||
import { MultiFactorAuthFactorsList } from './mfa/multi-factor-auth-list';
|
import { MultiFactorAuthFactorsList } from './mfa/multi-factor-auth-list';
|
||||||
import { UpdatePasswordFormContainer } from './password/update-password-container';
|
import { UpdatePasswordFormContainer } from './password/update-password-container';
|
||||||
@@ -150,6 +151,32 @@ export function PersonalAccountSettingsContainer(
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">
|
||||||
|
<Trans
|
||||||
|
i18nKey={'account:consentToAnonymizedCompanyData.label'}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CardDescription>
|
||||||
|
<Trans
|
||||||
|
i18nKey={'account:consentToAnonymizedCompanyData.description'}
|
||||||
|
/>
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<ConsentToggle
|
||||||
|
userId={props.userId}
|
||||||
|
initialState={
|
||||||
|
!!user.data.has_consent_anonymized_company_statistics
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<If condition={props.features.enableAccountDeletion}>
|
<If condition={props.features.enableAccountDeletion}>
|
||||||
<Card className={'border-destructive'}>
|
<Card className={'border-destructive'}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Switch } from '@kit/ui/switch';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import { useRevalidatePersonalAccountDataQuery } from '../../../hooks/use-personal-account-data';
|
||||||
|
import { useUpdateAccountData } from '../../../hooks/use-update-account';
|
||||||
|
|
||||||
|
// This is temporary. When the profile views are ready, all account values included in the form will be updated together on form submit.
|
||||||
|
export default function ConsentToggle({
|
||||||
|
userId,
|
||||||
|
initialState,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
initialState: boolean;
|
||||||
|
}) {
|
||||||
|
const [isConsent, setIsConsent] = useState(initialState);
|
||||||
|
const updateAccountMutation = useUpdateAccountData(userId);
|
||||||
|
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
|
||||||
|
|
||||||
|
const updateConsent = (consent: boolean) => {
|
||||||
|
const promise = updateAccountMutation
|
||||||
|
.mutateAsync({
|
||||||
|
has_consent_anonymized_company_statistics: consent,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
revalidateUserDataQuery(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return toast.promise(() => promise, {
|
||||||
|
success: <Trans i18nKey={'account:updateConsentSuccess'} />,
|
||||||
|
error: <Trans i18nKey={'account:updateConsentError'} />,
|
||||||
|
loading: <Trans i18nKey={'account:updateConsentLoading'} />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={isConsent}
|
||||||
|
onCheckedChange={setIsConsent}
|
||||||
|
onClick={() => updateConsent(!isConsent)}
|
||||||
|
disabled={updateAccountMutation.isPending}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,14 +22,7 @@ export function usePersonalAccountData(
|
|||||||
|
|
||||||
const response = await client
|
const response = await client
|
||||||
.from('accounts')
|
.from('accounts')
|
||||||
.select(
|
.select()
|
||||||
`
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
picture_url,
|
|
||||||
last_name
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
.eq('primary_owner_user_id', userId)
|
.eq('primary_owner_user_id', userId)
|
||||||
.eq('is_personal_account', true)
|
.eq('is_personal_account', true)
|
||||||
.single();
|
.single();
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export type Database = {
|
|||||||
created_at: string | null
|
created_at: string | null
|
||||||
created_by: string | null
|
created_by: string | null
|
||||||
email: string | null
|
email: string | null
|
||||||
|
has_consent_anonymized_company_statistics: boolean | null
|
||||||
has_consent_personal_data: boolean | null
|
has_consent_personal_data: boolean | null
|
||||||
id: string
|
id: string
|
||||||
is_personal_account: boolean
|
is_personal_account: boolean
|
||||||
@@ -208,6 +209,7 @@ export type Database = {
|
|||||||
created_at?: string | null
|
created_at?: string | null
|
||||||
created_by?: string | null
|
created_by?: string | null
|
||||||
email?: string | null
|
email?: string | null
|
||||||
|
has_consent_anonymized_company_statistics?: boolean | null
|
||||||
has_consent_personal_data?: boolean | null
|
has_consent_personal_data?: boolean | null
|
||||||
id?: string
|
id?: string
|
||||||
is_personal_account?: boolean
|
is_personal_account?: boolean
|
||||||
@@ -227,6 +229,7 @@ export type Database = {
|
|||||||
created_at?: string | null
|
created_at?: string | null
|
||||||
created_by?: string | null
|
created_by?: string | null
|
||||||
email?: string | null
|
email?: string | null
|
||||||
|
has_consent_anonymized_company_statistics?: boolean | null
|
||||||
has_consent_personal_data?: boolean | null
|
has_consent_personal_data?: boolean | null
|
||||||
id?: string
|
id?: string
|
||||||
is_personal_account?: boolean
|
is_personal_account?: boolean
|
||||||
@@ -1498,6 +1501,7 @@ export type Database = {
|
|||||||
created_at: string | null
|
created_at: string | null
|
||||||
created_by: string | null
|
created_by: string | null
|
||||||
email: string | null
|
email: string | null
|
||||||
|
has_consent_anonymized_company_statistics: boolean | null
|
||||||
has_consent_personal_data: boolean | null
|
has_consent_personal_data: boolean | null
|
||||||
id: string
|
id: string
|
||||||
is_personal_account: boolean
|
is_personal_account: boolean
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
export const SIDEBAR_WIDTH = '16rem';
|
export const SIDEBAR_WIDTH = '16rem';
|
||||||
export const SIDEBAR_WIDTH_MOBILE = '18rem';
|
export const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||||
export const SIDEBAR_WIDTH_ICON = '4rem';
|
export const SIDEBAR_WIDTH_ICON = '4rem';
|
||||||
|
|
||||||
|
export const SIDEBAR_WIDTH_PROPERTY = 'w-[16rem]';
|
||||||
|
export const SIDEBAR_WIDTH_MOBILE_PROPERTY = 'w-[18rem]';
|
||||||
|
export const SIDEBAR_WIDTH_ICON_PROPERTY = 'w-[4rem]';
|
||||||
|
|||||||
@@ -32,8 +32,16 @@ const DialogContent: React.FC<
|
|||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||||
customClose?: React.JSX.Element;
|
customClose?: React.JSX.Element;
|
||||||
preventAutoFocus?: boolean;
|
preventAutoFocus?: boolean;
|
||||||
|
disableClose?: boolean;
|
||||||
}
|
}
|
||||||
> = ({ className, children, customClose, preventAutoFocus, ...props }) => (
|
> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
customClose,
|
||||||
|
disableClose,
|
||||||
|
preventAutoFocus,
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
@@ -47,16 +55,26 @@ const DialogContent: React.FC<
|
|||||||
onCloseAutoFocus={
|
onCloseAutoFocus={
|
||||||
preventAutoFocus ? (e) => e.preventDefault() : props.onOpenAutoFocus
|
preventAutoFocus ? (e) => e.preventDefault() : props.onOpenAutoFocus
|
||||||
}
|
}
|
||||||
|
onInteractOutside={
|
||||||
|
disableClose ? (e) => e.preventDefault() : props.onInteractOutside
|
||||||
|
}
|
||||||
|
onPointerDownOutside={
|
||||||
|
disableClose ? (e) => e.preventDefault() : props.onPointerDownOutside
|
||||||
|
}
|
||||||
|
onEscapeKeyDown={
|
||||||
|
disableClose ? (e) => e.preventDefault() : props.onEscapeKeyDown
|
||||||
|
}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs transition-opacity hover:opacity-70 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs transition-opacity hover:opacity-70 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
{customClose || (
|
{!disableClose &&
|
||||||
<>
|
(customClose || (
|
||||||
<Cross2Icon className="h-4 w-4" />
|
<>
|
||||||
<span className="sr-only">Close</span>
|
<Cross2Icon className="h-4 w-4" />
|
||||||
</>
|
<span className="sr-only">Close</span>
|
||||||
)}
|
</>
|
||||||
|
))}
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
@@ -114,13 +132,13 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPortal,
|
|
||||||
DialogOverlay,
|
|
||||||
DialogTrigger,
|
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
|
||||||
DialogFooter,
|
|
||||||
DialogTitle,
|
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
public/assets/toggle.png
Normal file
BIN
public/assets/toggle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 713 KiB |
@@ -115,5 +115,12 @@
|
|||||||
"createCompanyAccount": "Create Company Account",
|
"createCompanyAccount": "Create Company Account",
|
||||||
"requestCompanyAccount": {
|
"requestCompanyAccount": {
|
||||||
"title": "Company details"
|
"title": "Company details"
|
||||||
|
},
|
||||||
|
"updateConsentSuccess": "Consent successfully updated",
|
||||||
|
"updateConsentError": "Encountered an error. Please try again",
|
||||||
|
"updateConsentLoading": "Updating consent...",
|
||||||
|
"consentToAnonymizedCompanyData": {
|
||||||
|
"label": "Consent to be included in employer statistics",
|
||||||
|
"description": "Consent to be included in anonymized company statistics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,5 +130,18 @@
|
|||||||
"successTitle": "Tere, {{firstName}} {{lastName}}",
|
"successTitle": "Tere, {{firstName}} {{lastName}}",
|
||||||
"successDescription": "Teie tervisekonto on aktiveeritud ja kasutamiseks valmis!",
|
"successDescription": "Teie tervisekonto on aktiveeritud ja kasutamiseks valmis!",
|
||||||
"successButton": "Jätka"
|
"successButton": "Jätka"
|
||||||
|
},
|
||||||
|
"consentModal": {
|
||||||
|
"title": "Enne toimetama hakkamist",
|
||||||
|
"description": "Kas annad nõusoleku, et sinu terviseandmeid kasutatakse anonüümselt tööandja statistikas? Andmed jäävad isikustamata ja aitavad ettevõttel töötajate tervist paremini toetada.",
|
||||||
|
"reject": "Ei anna nõusolekut",
|
||||||
|
"accept": "Annan nõusoleku"
|
||||||
|
},
|
||||||
|
"updateConsentSuccess": "Nõusolekud uuendatud",
|
||||||
|
"updateConsentError": "Midagi läks valesti. Palun proovi uuesti",
|
||||||
|
"updateConsentLoading": "Nõusolekuid uuendatakse...",
|
||||||
|
"consentToAnonymizedCompanyData": {
|
||||||
|
"label": "Nõustun osalema tööandja statistikas",
|
||||||
|
"description": "Nõustun anonümiseeritud kujul terviseandmete kasutamisega tööandja statistikas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,5 +112,12 @@
|
|||||||
"noTeamsYet": "You don't have any teams yet.",
|
"noTeamsYet": "You don't have any teams yet.",
|
||||||
"createTeam": "Create a team to get started.",
|
"createTeam": "Create a team to get started.",
|
||||||
"createTeamButtonLabel": "Create a Team",
|
"createTeamButtonLabel": "Create a Team",
|
||||||
"createCompanyAccount": "Create Company Account"
|
"createCompanyAccount": "Create Company Account",
|
||||||
|
"updateConsentSuccess": "Consent successfully updated",
|
||||||
|
"updateConsentError": "Encountered an error. Please try again",
|
||||||
|
"updateConsentLoading": "Updating consent...",
|
||||||
|
"consentToAnonymizedCompanyData": {
|
||||||
|
"label": "Consent to be included in employer statistics",
|
||||||
|
"description": "Consent to be included in anonymized company statistics"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
ALTER TABLE "public"."invitations" ADD COLUMN IF NOT EXISTS personal_code text;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
alter table "public"."accounts" add column "has_consent_anonymized_company_statistics" boolean;
|
||||||
|
|
||||||
|
alter table "audit"."log_entries" alter column "record_key" set data type text using "record_key"::text;
|
||||||
|
|
||||||
|
create policy "insert_own"
|
||||||
|
on "audit"."log_entries"
|
||||||
|
as permissive
|
||||||
|
for insert
|
||||||
|
to authenticated
|
||||||
|
with check ((( SELECT auth.uid() AS uid) = changed_by));
|
||||||
|
|
||||||
|
drop policy "service_role_all" on "audit"."sync_entries";
|
||||||
|
|
||||||
|
create policy "service_role_all"
|
||||||
|
on "audit"."sync_entries"
|
||||||
|
as permissive
|
||||||
|
for all
|
||||||
|
to service_role
|
||||||
|
using (true);
|
||||||
|
|
||||||
|
CREATE TRIGGER log_account_change AFTER DELETE OR UPDATE ON public.accounts FOR EACH ROW EXECUTE FUNCTION audit.log_audit_changes();
|
||||||
|
GRANT USAGE ON SCHEMA audit TO authenticated;
|
||||||
|
grant insert on table audit.log_entries to authenticated;
|
||||||
Reference in New Issue
Block a user