diff --git a/app/modules/member-schema.test.ts b/app/modules/member-schema.test.ts new file mode 100644 index 00000000..9ed4570a --- /dev/null +++ b/app/modules/member-schema.test.ts @@ -0,0 +1,183 @@ +// @vitest-environment node + +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import Markdoc from '@markdoc/markdoc'; +import yaml from 'js-yaml'; +import { describe, expect, it } from 'vitest'; + +import { CHALLENGE_KEYS } from './challenge-schema'; +import { MemberFrontmatterSchema } from './schemas'; + +// Base fields are also covered in `schemas.test.ts`; this file covers the +// posts files and the expert fields of ocobo-revops/posts#95. +// +// Copies of every `team/*.md` file of ocobo-revops/posts (main b34a449). None +// sets an expert field yet. Refresh them when the posts Team member schema +// changes or when expert fields arrive in `team/*.md`. +const FIXTURES_DIR = join(__dirname, '../test/fixtures/content/team'); + +// Same frontmatter parsing as the content pipeline (`content/processor.ts`). +const loadFixture = (file: string): Record => { + const markdown = readFileSync(join(FIXTURES_DIR, file), 'utf8'); + const { frontmatter } = Markdoc.parse(markdown).attributes; + return yaml.load(frontmatter, { schema: yaml.CORE_SCHEMA }) as Record< + string, + any + >; +}; + +const messagesOf = (data: unknown) => { + const result = MemberFrontmatterSchema.safeParse(data); + return result.success ? [] : result.error.issues.map((i) => i.message); +}; + +const accepts = (data: unknown) => + MemberFrontmatterSchema.safeParse(data).success; + +describe('Team member fixtures', () => { + const files = readdirSync(FIXTURES_DIR).filter((f) => f.endsWith('.md')); + + it('has the posts team files', () => { + expect(files.length).toBeGreaterThan(0); + }); + + it.each(files)('validates %s', (file) => { + expect(messagesOf(loadFixture(file))).toEqual([]); + }); +}); + +describe('Team member expert fields', () => { + const base = loadFixture('benjamin-boileux.md'); + const expert = { + ...base, + challenge: 'sc', + contribution: { fr: 'Contribution', en: 'Contribution EN' }, + tools: ['hubspot', 'qobra'], + previousCompanies: ['Qonto'], + }; + + it('accepts a member with all four expert fields', () => { + const result = MemberFrontmatterSchema.safeParse(expert); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).toMatchObject({ + challenge: 'sc', + contribution: expert.contribution, + tools: ['hubspot', 'qobra'], + previousCompanies: ['Qonto'], + }); + } + }); + + it.each(CHALLENGE_KEYS)('accepts the challenge key %s', (challenge) => { + expect(accepts({ ...expert, challenge })).toBe(true); + }); + + it('rejects an unknown challenge key', () => { + expect(accepts({ ...expert, challenge: 'xx' })).toBe(false); + }); + + it.each(['contribution', 'tools'] as const)( + 'rejects challenge without %s', + (field) => { + const { [field]: _omitted, ...rest } = expert; + const result = MemberFrontmatterSchema.safeParse(rest); + expect(result.success).toBe(false); + expect( + result.error?.issues.map(({ path, message }) => ({ path, message })), + ).toEqual([ + { + path: [field], + message: `${field} is required when challenge is set`, + }, + ]); + }, + ); + + it('accepts challenge without previousCompanies', () => { + const { previousCompanies: _omitted, ...rest } = expert; + expect(accepts(rest)).toBe(true); + }); + + it('accepts contribution, tools and previousCompanies without challenge', () => { + const { challenge: _omitted, ...rest } = expert; + expect(accepts(rest)).toBe(true); + }); + + it('caps contribution at 100 characters per language', () => { + const ok = 'x'.repeat(100); + const tooLong = 'x'.repeat(101); + expect(accepts({ ...expert, contribution: { fr: ok, en: ok } })).toBe(true); + expect(accepts({ ...expert, contribution: { fr: tooLong, en: ok } })).toBe( + false, + ); + expect(accepts({ ...expert, contribution: { fr: ok, en: tooLong } })).toBe( + false, + ); + }); + + it('applies the contribution cap to the raw text', () => { + const contribution = { fr: `${'x'.repeat(100)} `, en: 'ok' }; + expect(accepts({ ...expert, contribution })).toBe(false); + }); + + it('rejects a contribution missing a language', () => { + expect(accepts({ ...expert, contribution: { fr: 'Contribution' } })).toBe( + false, + ); + }); + + it('accepts 1 to 8 tools and rejects 0 or 9', () => { + const slugs = (n: number) => Array.from({ length: n }, (_, i) => `t-${i}`); + expect(accepts({ ...expert, tools: slugs(1) })).toBe(true); + expect(accepts({ ...expert, tools: slugs(8) })).toBe(true); + expect(accepts({ ...expert, tools: [] })).toBe(false); + expect(accepts({ ...expert, tools: slugs(9) })).toBe(false); + }); + + it('keeps the tools order', () => { + const tools = ['qobra', 'hubspot', 'notion']; + const result = MemberFrontmatterSchema.safeParse({ ...expert, tools }); + expect(result.success && result.data.tools).toEqual(tools); + }); + + it('rejects duplicate tools', () => { + expect(messagesOf({ ...expert, tools: ['hubspot', 'hubspot'] })).toContain( + 'tools must not contain duplicates', + ); + }); + + it('rejects a blank contribution or previous company', () => { + const contribution = { fr: ' ', en: 'Contribution' }; + expect(accepts({ ...expert, contribution })).toBe(false); + expect(accepts({ ...expert, previousCompanies: [' '] })).toBe(false); + }); + + it('accepts 1 to 3 previous companies and rejects 0 or 4', () => { + const four = ['A', 'B', 'C', 'D']; + expect(accepts({ ...expert, previousCompanies: ['A', 'B', 'C'] })).toBe( + true, + ); + expect(accepts({ ...expert, previousCompanies: [] })).toBe(false); + expect(accepts({ ...expert, previousCompanies: four })).toBe(false); + }); + + it('caps a previous company name at 30 characters', () => { + const name = (n: number) => 'x'.repeat(n); + expect(accepts({ ...expert, previousCompanies: [name(30)] })).toBe(true); + expect(accepts({ ...expert, previousCompanies: [name(31)] })).toBe(false); + }); + + it('drops a quote key: bio stays the only about-us text', () => { + const result = MemberFrontmatterSchema.safeParse({ + ...expert, + quote: 'Ignored', + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).not.toHaveProperty('quote'); + expect(result.data.bio).toEqual(base.bio); + } + }); +}); diff --git a/app/modules/schemas.ts b/app/modules/schemas.ts index 7ddf1a1d..061e048e 100644 --- a/app/modules/schemas.ts +++ b/app/modules/schemas.ts @@ -8,6 +8,8 @@ import { z } from 'zod'; +import { CHALLENGE_KEYS } from './challenge-schema'; + /** * Common validation schemas that can be reused */ @@ -172,24 +174,61 @@ export type MemberTrack = z.infer; /** * Team member frontmatter schema — one file per person in `team/.md` */ -export const MemberFrontmatterSchema = z.object({ - name: CommonSchemas.nonEmptyString, - role: z.object({ - fr: CommonSchemas.nonEmptyString, - en: CommonSchemas.nonEmptyString, - }), - track: MemberTrackSchema, - linkedin: z.string().url().optional(), - avatar: z.string().url(), - displayOrder: z.number().int(), - active: z.boolean().default(true), - bio: z.object({ - fr: CommonSchemas.nonEmptyString, - en: CommonSchemas.nonEmptyString, - }), - featuredOnAboutUs: z.boolean().default(false), - color: MemberColorSchema.optional(), -}); +export const MemberFrontmatterSchema = z + .object({ + name: CommonSchemas.nonEmptyString, + role: z.object({ + fr: CommonSchemas.nonEmptyString, + en: CommonSchemas.nonEmptyString, + }), + track: MemberTrackSchema, + linkedin: z.string().url().optional(), + avatar: z.string().url(), + displayOrder: z.number().int(), + active: z.boolean().default(true), + bio: z.object({ + fr: CommonSchemas.nonEmptyString, + en: CommonSchemas.nonEmptyString, + }), + featuredOnAboutUs: z.boolean().default(false), + color: MemberColorSchema.optional(), + // Expert fields: all optional, absent for people who are not challenge + // experts. Mirrors the posts repo (`scripts/schemas/team-member.schema.js`, + // ocobo-revops/posts#95). Caps apply to the raw text, as published, so no + // trim; `\S` rejects blank values. + challenge: z.enum(CHALLENGE_KEYS).optional(), + contribution: z + .object({ + fr: z.string().regex(/\S/).max(100), + en: z.string().regex(/\S/).max(100), + }) + .optional(), + tools: z + .array(z.string().min(1)) + .min(1) + .max(8) + .refine((slugs) => new Set(slugs).size === slugs.length, { + message: 'tools must not contain duplicates', + }) + .optional(), + previousCompanies: z + .array(z.string().regex(/\S/).max(30)) + .min(1) + .max(3) + .optional(), + }) + .superRefine((member, ctx) => { + if (member.challenge === undefined) return; + for (const field of ['contribution', 'tools'] as const) { + if (member[field] === undefined) { + ctx.addIssue({ + code: 'custom', + path: [field], + message: `${field} is required when challenge is set`, + }); + } + } + }); /** * Tool frontmatter schema — one file per tool in `tools/.md` diff --git a/app/test/fixtures/content/team/aude-cadiot.md b/app/test/fixtures/content/team/aude-cadiot.md new file mode 100644 index 00000000..53a88bbc --- /dev/null +++ b/app/test/fixtures/content/team/aude-cadiot.md @@ -0,0 +1,16 @@ +--- +name: Aude Cadiot +role: + fr: Associée + en: Partner +track: architect +linkedin: https://www.linkedin.com/in/aude-cadiot-9597096b/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/aude-cadiot.jpg +displayOrder: 2 +active: true +featuredOnAboutUs: true +color: coral +bio: + fr: Référence française du Revenue Operations (TheFork, Spendesk). Experte du customer journey, des organisations GTM, du scaling d'équipes RevOps. + en: France's Revenue Operations reference (TheFork, Spendesk). Expert in customer journey, GTM organisations, and scaling RevOps teams. +--- diff --git a/app/test/fixtures/content/team/benjamin-boileux.md b/app/test/fixtures/content/team/benjamin-boileux.md new file mode 100644 index 00000000..1c955057 --- /dev/null +++ b/app/test/fixtures/content/team/benjamin-boileux.md @@ -0,0 +1,16 @@ +--- +name: Benjamin Boileux +role: + fr: Associé + en: Partner +track: architect +linkedin: https://www.linkedin.com/in/benjaminboileux/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/benjamin-boileux.jpg +displayOrder: 1 +active: true +featuredOnAboutUs: true +color: yellow +bio: + fr: 12+ ans en Ops, CRM, Product et Revenue Operations (TheFork, Tripadvisor, Yousign). Spécialiste des systèmes et du pilotage opérationnel. + en: 12+ years in Ops, CRM, Product and Revenue Operations (TheFork, Tripadvisor, Yousign). Systems and operational management specialist. +--- diff --git a/app/test/fixtures/content/team/clara-lecarrie.md b/app/test/fixtures/content/team/clara-lecarrie.md new file mode 100644 index 00000000..3cacacb0 --- /dev/null +++ b/app/test/fixtures/content/team/clara-lecarrie.md @@ -0,0 +1,15 @@ +--- +name: Clara Lecarrié +role: + fr: Manager + en: Manager +track: architect +linkedin: https://www.linkedin.com/in/clara-lecarrie-070595106/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/clara-lecarrie.jpg +displayOrder: 4 +active: true +featuredOnAboutUs: false +bio: + fr: Spécialiste de l'expérience utilisateur et de l'optimisation des parcours de vente complexes. + en: Specialist in user experience and optimising complex sales journeys. +--- diff --git a/app/test/fixtures/content/team/clementine-du-pradel.md b/app/test/fixtures/content/team/clementine-du-pradel.md new file mode 100644 index 00000000..71b291ba --- /dev/null +++ b/app/test/fixtures/content/team/clementine-du-pradel.md @@ -0,0 +1,16 @@ +--- +slug: clementine-du-pradel +name: Clémentine du Pradel +track: architect +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/clementine-du-pradel.png +role: + fr: Manager + en: Manager +bio: + fr: Experte en pilotage de projets complexes et optimisation des processus, pour aligner les parties prenantes et transformer les opérations en leviers de croissance. + en: Expert in managing complex projects and optimising processes, to align stakeholders and transform operations into growth drivers. +linkedin: "https://www.linkedin.com/in/clémentine-du-pradel-237ab948/" +active: true +featuredOnAboutUs: false +displayOrder: 12 +--- diff --git a/app/test/fixtures/content/team/corentin-guerin.md b/app/test/fixtures/content/team/corentin-guerin.md new file mode 100644 index 00000000..58af2399 --- /dev/null +++ b/app/test/fixtures/content/team/corentin-guerin.md @@ -0,0 +1,16 @@ +--- +name: Corentin Guérin +role: + fr: Associé + en: Partner +track: architect +linkedin: https://www.linkedin.com/in/corentinguerin/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/corentin-guerin.jpeg +displayOrder: 3 +active: true +featuredOnAboutUs: true +color: sky +bio: + fr: Finance, Deloitte, Business Ops (TheFork). Spécialiste du forecasting, de la performance, des rémunérations variables et du pricing. + en: Finance, Deloitte, Business Ops (TheFork). Forecasting, performance, variable compensation and pricing specialist. +--- diff --git a/app/test/fixtures/content/team/damien-robert.md b/app/test/fixtures/content/team/damien-robert.md new file mode 100644 index 00000000..de5e820d --- /dev/null +++ b/app/test/fixtures/content/team/damien-robert.md @@ -0,0 +1,15 @@ +--- +name: Damien Robert +role: + fr: RevOps Manager + en: RevOps Manager +track: builder +linkedin: https://www.linkedin.com/in/damien-robert-b3438310a/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/damien-robert.jpg +displayOrder: 9 +active: true +featuredOnAboutUs: false +bio: + fr: Spécialiste de la performance commerciale et du pilotage par la donnée fiable. + en: Specialist in commercial performance and data-driven management. +--- diff --git a/app/test/fixtures/content/team/domitille-raimbault.md b/app/test/fixtures/content/team/domitille-raimbault.md new file mode 100644 index 00000000..135806d3 --- /dev/null +++ b/app/test/fixtures/content/team/domitille-raimbault.md @@ -0,0 +1,15 @@ +--- +name: Domitille Raimbault +role: + fr: Senior RevOps Manager + en: Senior RevOps Manager +track: builder +linkedin: https://www.linkedin.com/in/domitille-raimbault-488366107/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/domitille-raimbault.jpeg +displayOrder: 6 +active: true +featuredOnAboutUs: false +bio: + fr: Maîtrise les architectures CRM les plus complexes pour transformer la donnée en levier de croissance. + en: Masters the most complex CRM architectures to turn data into a growth lever. +--- diff --git a/app/test/fixtures/content/team/dorian-cutullic.md b/app/test/fixtures/content/team/dorian-cutullic.md new file mode 100644 index 00000000..ba0d122e --- /dev/null +++ b/app/test/fixtures/content/team/dorian-cutullic.md @@ -0,0 +1,14 @@ +--- +name: Dorian Cutullic +role: + fr: RevOps Manager + en: RevOps Manager +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/dorian-cutullic.jpg +displayOrder: 8 +active: true +featuredOnAboutUs: false +bio: + fr: Accompagne les scale-ups dans la structuration de leur stack et l'adoption des rituels RevOps. + en: Supports scale-ups in structuring their stack and adopting RevOps rituals. +--- diff --git a/app/test/fixtures/content/team/edmond-de-maistre.md b/app/test/fixtures/content/team/edmond-de-maistre.md new file mode 100644 index 00000000..79aa70d3 --- /dev/null +++ b/app/test/fixtures/content/team/edmond-de-maistre.md @@ -0,0 +1,16 @@ +--- +slug: edmond-de-maistre +name: Edmond de Maistre +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/edmond-de-maistre.png +role: + fr: Revenue Operations Associate + en: Revenue Operations Associate +bio: + fr: Spécialiste de la performance commerciale et du pilotage par la donnée fiable. + en: Specialist in commercial performance and reliable, data-driven decision-making. +linkedin: "https://www.linkedin.com/in/edmond-de-maistre/" +active: true +featuredOnAboutUs: false +displayOrder: 13 +--- diff --git a/app/test/fixtures/content/team/ethel-gosset.md b/app/test/fixtures/content/team/ethel-gosset.md new file mode 100644 index 00000000..3922aa56 --- /dev/null +++ b/app/test/fixtures/content/team/ethel-gosset.md @@ -0,0 +1,15 @@ +--- +name: Ethel Gosset +role: + fr: Senior RevOps Manager + en: Senior RevOps Manager +track: builder +linkedin: https://www.linkedin.com/in/ethel-gosset-a4b295136/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/ethel-gosset.jpeg +displayOrder: 7 +active: false +featuredOnAboutUs: false +bio: + fr: Experte en automatisation et fluidification des processus opérationnels Sales & Marketing. + en: Expert in automating and streamlining Sales & Marketing operational processes. +--- diff --git a/app/test/fixtures/content/team/hugo-moulon.md b/app/test/fixtures/content/team/hugo-moulon.md new file mode 100644 index 00000000..c97f3a53 --- /dev/null +++ b/app/test/fixtures/content/team/hugo-moulon.md @@ -0,0 +1,16 @@ +--- +slug: hugo-moulon +name: Hugo Moulon +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/hugo-moulon.jpeg +role: + fr: RevOps Manager + en: RevOps Manager +bio: + fr: Expert dans la structuration et l'optimisation des processus Sales et CSM, de la compréhension métier à l'adaptation du CRM jusqu'au pilotage par la donnée, pour accélérer la croissance et éliminer les frictions opérationnelles. + en: Expert in structuring and optimising Sales and CSM processes — from grasping the business needs to tailoring the CRM and steering by data — to accelerate growth and remove operational friction. +linkedin: "https://www.linkedin.com/in/hugo-moulon-44b4a613b/" +active: true +featuredOnAboutUs: false +displayOrder: 14 +--- diff --git a/app/test/fixtures/content/team/jeanne-hebben.md b/app/test/fixtures/content/team/jeanne-hebben.md new file mode 100644 index 00000000..181f697e --- /dev/null +++ b/app/test/fixtures/content/team/jeanne-hebben.md @@ -0,0 +1,16 @@ +--- +slug: jeanne-hebben +name: Jeanne Hebben +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/jeanne-hebben.png +role: + fr: Senior Revenue Operations Manager + en: Senior Revenue Operations Manager +bio: + fr: Experte dans l'optimisation et l'automatisation des process Sales pour aligner stratégie commerciale et performance opérationnelle. + en: Expert in optimising and automating Sales processes to align commercial strategy with operational performance. +linkedin: "https://www.linkedin.com/in/jeanne-hebben/" +active: true +featuredOnAboutUs: false +displayOrder: 15 +--- diff --git a/app/test/fixtures/content/team/louis-strauss.md b/app/test/fixtures/content/team/louis-strauss.md new file mode 100644 index 00000000..a7d39e89 --- /dev/null +++ b/app/test/fixtures/content/team/louis-strauss.md @@ -0,0 +1,15 @@ +--- +name: Louis Strauss +role: + fr: RevOps Engineer + en: RevOps Engineer +track: expert-engineer +linkedin: https://www.linkedin.com/in/louis-strauss1/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/louis-strauss.jpg +displayOrder: 5 +active: true +featuredOnAboutUs: false +bio: + fr: Le cerveau technique derrière nos intégrations les plus poussées et nos outils propriétaires. + en: The technical brain behind our most advanced integrations and proprietary tools. +--- diff --git a/app/test/fixtures/content/team/mariette-baudras.md b/app/test/fixtures/content/team/mariette-baudras.md new file mode 100644 index 00000000..775592d4 --- /dev/null +++ b/app/test/fixtures/content/team/mariette-baudras.md @@ -0,0 +1,15 @@ +--- +name: Mariette Baudras +role: + fr: RevOps Manager + en: RevOps Manager +track: builder +linkedin: https://www.linkedin.com/in/mariette-baudras-bove/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/mariette-baudras.jpg +displayOrder: 10 +active: true +featuredOnAboutUs: false +bio: + fr: Experte en Customer Success Operations et optimisation de la rétention client. + en: Expert in Customer Success Operations and client retention optimisation. +--- diff --git a/app/test/fixtures/content/team/morgane-philbert.md b/app/test/fixtures/content/team/morgane-philbert.md new file mode 100644 index 00000000..c6c2ebae --- /dev/null +++ b/app/test/fixtures/content/team/morgane-philbert.md @@ -0,0 +1,16 @@ +--- +slug: morgane-philbert +name: Morgane Philbert +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/morgane-philbert.jpg +role: + fr: Brand & Communication Manager + en: Brand & Communication Manager +bio: + fr: "Experte en marque et contenu : ligne éditoriale, podcast, événementiel et partenariats. Pilote aussi l'acquisition et le marketing operations d'Ocobo." + en: "Brand and content expert: editorial strategy, podcast, events and partnerships. Also drives Ocobo's acquisition and marketing operations." +linkedin: "https://www.linkedin.com/in/morganephilbert/" +active: true +featuredOnAboutUs: false +displayOrder: 17 +--- diff --git a/app/test/fixtures/content/team/pauline-belaubre.md b/app/test/fixtures/content/team/pauline-belaubre.md new file mode 100644 index 00000000..4117b177 --- /dev/null +++ b/app/test/fixtures/content/team/pauline-belaubre.md @@ -0,0 +1,16 @@ +--- +slug: pauline-belaubre +name: Pauline Belaubre +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/pauline-belaubre.jpg +role: + fr: RevOps Manager + en: RevOps Manager +bio: + fr: "Spécialiste de la structuration des process et du CRM, et de leur adoption par les équipes, pour gagner en efficacité et en fiabilité au quotidien." + en: "Specialist in structuring processes and the CRM, and in driving their adoption by teams, to gain efficiency and reliability day to day." +linkedin: "https://www.linkedin.com/in/pauline-belaubre/" +active: true +featuredOnAboutUs: false +displayOrder: 18 +--- diff --git a/app/test/fixtures/content/team/thomas-sionville.md b/app/test/fixtures/content/team/thomas-sionville.md new file mode 100644 index 00000000..47c0f3a3 --- /dev/null +++ b/app/test/fixtures/content/team/thomas-sionville.md @@ -0,0 +1,16 @@ +--- +slug: thomas-sionville +name: Thomas Sionville +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/thomas-sionville.png +role: + fr: RevOps Manager + en: RevOps Manager +bio: + fr: Spécialiste de l'automatisation des processus et de la structuration des données Sales et CSM. + en: Specialist in process automation and in structuring Sales and CSM data. +linkedin: "https://www.linkedin.com/in/thomas-revops/" +active: true +featuredOnAboutUs: false +displayOrder: 16 +--- diff --git a/app/test/fixtures/content/team/victor-lajri.md b/app/test/fixtures/content/team/victor-lajri.md new file mode 100644 index 00000000..7498de91 --- /dev/null +++ b/app/test/fixtures/content/team/victor-lajri.md @@ -0,0 +1,16 @@ +--- +slug: victor-lajri +name: Victor Lajri +track: builder +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/victor-lajri.jpg +role: + fr: RevOps Manager + en: RevOps Manager +bio: + fr: Expert en optimisation CRM et fiabilisation des données Sales, pour libérer du temps de vente aux équipes commerciales. + en: Expert in CRM optimisation and Sales data reliability, freeing up selling time for sales teams. +linkedin: "https://www.linkedin.com/in/victor-lajri-a8a993115/" +active: true +featuredOnAboutUs: false +displayOrder: 19 +--- diff --git a/app/test/fixtures/content/team/yoann-boileux.md b/app/test/fixtures/content/team/yoann-boileux.md new file mode 100644 index 00000000..a9f085a5 --- /dev/null +++ b/app/test/fixtures/content/team/yoann-boileux.md @@ -0,0 +1,15 @@ +--- +name: Yoann Boileux +role: + fr: RevOps Manager + en: RevOps Manager +track: builder +linkedin: https://www.linkedin.com/in/yoann-boileux/ +avatar: https://ipjmp3k0z0p479cb.public.blob.vercel-storage.com/content/team/yoann-boileux.jpeg +displayOrder: 11 +active: true +featuredOnAboutUs: false +bio: + fr: Focus sur l'enablement des équipes et la création de playbooks d'exécution robustes. + en: Focused on team enablement and creating robust execution playbooks. +---