Merge pull request #94 from learnhouse/swve/eng-70-migrate-settings-pages-to-server-components

Migrate settings to server components
This commit is contained in:
Badr B 2023-06-15 19:50:49 +02:00 committed by GitHub
commit d0d034eaad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 358 additions and 263 deletions

View file

@ -1,77 +1,30 @@
"use client"; import { getOrganizationContextInfo } from "@services/organizations/orgs";
import { AuthContext } from '@components/Security/AuthProvider'; import { Metadata } from "next";
import React, { useEffect } from 'react' import PasswordsClient from "./passwords";
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { updateProfile } from '@services/settings/profile'; type MetadataProps = {
import { updatePassword } from '@services/settings/password'; params: { orgslug: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
return {
title: `Settings: Passwords — ${org.name}`,
description: org.description,
};
}
function SettingsProfilePasswordsPage() { function SettingsProfilePasswordsPage() {
return (
const auth: any = React.useContext(AuthContext); <>
<PasswordsClient></PasswordsClient>
const updatePasswordUI = async (values: any) => { </>
let user_id = auth.userInfo.user_object.user_id; )
console.log(values);
await updatePassword(user_id, values)
}
return (
<div>
{auth.isAuthenticated && (
<div>
<h1 className='text-3xl font-bold'>Account Password</h1>
<br /><br />
<Formik
initialValues={{ old_password: '', new_password: '' }}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updatePasswordUI(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form className="max-w-md">
<label className="block mb-2 font-bold" htmlFor="old_password">
Old Password
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="password"
name="old_password"
/>
<label className="block mb-2 font-bold" htmlFor="new_password">
New Password
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="password"
name="new_password"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Submit
</button>
</Form>
)}
</Formik>
</div>
)}
</div>
)
} }
export default SettingsProfilePasswordsPage export default SettingsProfilePasswordsPage

View file

@ -0,0 +1,77 @@
"use client";
import { AuthContext } from '@components/Security/AuthProvider';
import React, { useEffect } from 'react'
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { updatePassword } from '@services/settings/password';
function PasswordsClient() {
const auth: any = React.useContext(AuthContext);
console.log('auth', auth)
const updatePasswordUI = async (values: any) => {
let user_id = auth.userInfo.user_object.user_id;
await updatePassword(user_id, values)
}
return (
<div>
{auth.isAuthenticated && (
<div>
<h1 className='text-3xl font-bold'>Account Password</h1>
<br /><br />
<Formik
initialValues={{ old_password: '', new_password: '' }}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updatePasswordUI(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form className="max-w-md">
<label className="block mb-2 font-bold" htmlFor="old_password">
Old Password
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="password"
name="old_password"
/>
<label className="block mb-2 font-bold" htmlFor="new_password">
New Password
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="password"
name="new_password"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Submit
</button>
</Form>
)}
</Formik>
</div>
)}
</div>
)
}
export default PasswordsClient

View file

@ -1,79 +1,26 @@
"use client"; import { getOrganizationContextInfo } from "@services/organizations/orgs";
import { AuthContext } from '@components/Security/AuthProvider'; import { Metadata } from "next";
import React, { useEffect } from 'react' import ProfileClient from "./profile";
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { updateProfile } from '@services/settings/profile'; type MetadataProps = {
params: { orgslug: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
return {
title: `Settings: Profile — ${org.name}`,
description: org.description,
};
}
function SettingsProfilePage() { function SettingsProfilePage() {
const auth: any = React.useContext(AuthContext); return <ProfileClient></ProfileClient>
return (
<div>
{auth.isAuthenticated && (
<div>
<h1 className='text-3xl font-bold'>Profile Settings</h1>
<br /><br />
<Formik
initialValues={auth.userInfo.user_object}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updateProfile(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form className="max-w-md">
<label className="block mb-2 font-bold" htmlFor="full_name">
Full Name
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="textarea"
name="full_name"
/>
<label className="block mb-2 font-bold" htmlFor="email">
Email
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="email"
name="email"
/>
<label className="block mb-2 font-bold" htmlFor="bio">
Bio
</label>
<Field
as="textarea"
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="textarea"
name="bio"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Submit
</button>
</Form>
)}
</Formik>
</div>
)}
</div>
)
} }
export default SettingsProfilePage export default SettingsProfilePage

View file

@ -0,0 +1,79 @@
"use client";
import { AuthContext } from '@components/Security/AuthProvider';
import React, { useEffect } from 'react'
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { updateProfile } from '@services/settings/profile';
function ProfileClient() {
const auth: any = React.useContext(AuthContext);
return (
<div>
{auth.isAuthenticated && (
<div>
<h1 className='text-3xl font-bold'>Profile Settings</h1>
<br /><br />
<Formik
initialValues={auth.userInfo.user_object}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updateProfile(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form className="max-w-md">
<label className="block mb-2 font-bold" htmlFor="full_name">
Full Name
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="textarea"
name="full_name"
/>
<label className="block mb-2 font-bold" htmlFor="email">
Email
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="email"
name="email"
/>
<label className="block mb-2 font-bold" htmlFor="bio">
Bio
</label>
<Field
as="textarea"
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="textarea"
name="bio"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Submit
</button>
</Form>
)}
</Formik>
</div>
)}
</div>
)
}
export default ProfileClient

View file

@ -0,0 +1,101 @@
"use client";
import React from 'react'
import { Field, Form, Formik } from 'formik';
import { updateOrganization } from '@services/settings/org';
interface OrganizationValues {
name: string;
description: string;
slug: string;
email: string;
}
function OrganizationClient(props: any) {
const org = props.org;
let orgValues: OrganizationValues = {
name: org.name,
description: org.description,
slug: org.slug,
email: org.email
}
const updateOrg = async (values: OrganizationValues) => {
let org_id = org.org_id;
await updateOrganization(org_id, values);
}
return (
<div>
<h1 className='text-3xl font-bold'>Organization Settings</h1>
<br /><br />
<Formik
initialValues={orgValues}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updateOrg(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form>
<label className="block mb-2 font-bold" htmlFor="name">
Name
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
name="name"
/>
<label className="block mb-2 font-bold" htmlFor="description">
Description
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
name="description"
/>
<label className="block mb-2 font-bold" htmlFor="slug">
Slug
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg bg-gray-200 cursor-not-allowed"
disabled
type="text"
name="slug"
/>
<label className="block mb-2 font-bold" htmlFor="email">
Email
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="email"
name="email"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-black"
>
Submit
</button>
</Form>
)}
</Formik>
</div>
)
}
export default OrganizationClient

View file

@ -1,115 +1,33 @@
"use client"; import { getOrganizationContextInfo } from '@services/organizations/orgs';
import React from 'react' import { Metadata } from 'next';
import useSWR, { mutate } from "swr"; import OrganizationClient from './organization';
import { swrFetcher } from "@services/utils/ts/requests";
import { getAPIUrl } from '@services/config/config';
import { Field, Form, Formik } from 'formik';
import { updateOrganization } from '@services/settings/org';
interface OrganizationValues { type MetadataProps = {
name: string; params: { orgslug: string };
description: string; searchParams: { [key: string]: string | string[] | undefined };
slug: string; };
email: string;
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
return {
title: `Settings: General — ${org.name}`,
description: org.description,
};
} }
function SettingsOrganizationGeneral(params: any) { async function SettingsOrganizationGeneral(params: any) {
const orgslug = params.params.orgslug; const orgslug = params.params.orgslug;
const { data: org, error: error } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher); const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
if (org) { return (
let orgValues: OrganizationValues = { <>
name: org.name, <OrganizationClient org={org} />
description: org.description, </>
slug: org.slug, )
email: org.email
}
const updateOrg = async (values: OrganizationValues) => {
let org_id = org.org_id;
await updateOrganization(org_id, values);
// Sounds good, doesn't work
// TODO: Fix this
mutate(`${getAPIUrl()}orgs/slug/${values.slug}`);
}
return (
<div>
<h1 className='text-3xl font-bold'>Oganization Settings</h1>
<br /><br />
{error && <p>Failed to load</p>}
{!org ? (
<div>Loading...</div>
) : (
<Formik
initialValues={orgValues}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
updateOrg(values)
}, 400);
}}
>
{({ isSubmitting }) => (
<Form>
<label className="block mb-2 font-bold" htmlFor="name">
Name
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
name="name"
/>
<label className="block mb-2 font-bold" htmlFor="description">
Description
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
name="description"
/>
<label className="block mb-2 font-bold" htmlFor="slug">
Slug
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg bg-gray-200 cursor-not-allowed"
disabled
type="text"
name="slug"
/>
<label className="block mb-2 font-bold" htmlFor="email">
Email
</label>
<Field
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
type="email"
name="email"
/>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-black"
>
Submit
</button>
</Form>
)}
</Formik>
)}
</div>
)
}
} }
export default SettingsOrganizationGeneral export default SettingsOrganizationGeneral

View file

@ -1,4 +1,23 @@
import React from 'react' import { getOrganizationContextInfo } from "@services/organizations/orgs";
import { Metadata, ResolvingMetadata } from 'next';
type MetadataProps = {
params: { orgslug: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
return {
title: `Settings — ${org.name}`,
description: org.description,
};
}
function Settings() { function Settings() {
return ( return (

View file

@ -1,5 +1,7 @@
import json import json
from typing import Optional
from uuid import uuid4 from uuid import uuid4
from click import Option
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users.schemas.users import UserOrganization from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
@ -14,7 +16,7 @@ class Organization(BaseModel):
description: str description: str
email: str email: str
slug: str slug: str
default: bool default: Optional[bool]
class OrganizationInDB(Organization): class OrganizationInDB(Organization):
@ -106,11 +108,8 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu
org = await orgs.find_one({"org_id": org_id}) org = await orgs.find_one({"org_id": org_id})
if org: if not org:
org["owners"]
org["admins"]
else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
@ -185,12 +184,14 @@ async def verify_org_rights(request: Request, org_id: str, current_user: Public
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
isOwner = current_user.user_id in org["owners"] # check if is owner of org
# todo check if is admin of org
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, org_id, org_id) hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, org_id, org_id)
if not hasRoleRights and not isOwner: # if not hasRoleRights and not isOwner:
raise HTTPException( # raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this organization") # status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this organization")
return True return True